Search code examples
androidandroid-intentandroid-manifestintentfilterapplinks

Android App Links with multiple domains launching same activity


This is my AndroidManifest.xml file

<activity
    android:name=".activity.LaunchActivity"
    android:autoVerify="true"
    android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
    android:label="@string/app_name"
    android:noHistory="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:host="subdomain1.domain.ext"
            android:path="/path1/subpath1/.*"
            android:scheme="https" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:host="subdomain1.domain.ext"
            android:pathPattern="/path2/subpath2/..*"
            android:scheme="https" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data
            android:host="subdomain2.subdomain1.domain.ext"
            android:pathPattern="^..*"
            android:scheme="https" />
    </intent-filter>
</activity>
<activity
    android:name=".activity.LoginActivity"
    android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
    android:label="@string/app_name" />
<activity
    android:name=".activity.MainActivity"
    android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
    android:label="@string/app_name" />

I want to use Android App Links for 2 domains,

  1. subdomain1.domain.ext
  2. subdomain2.subdomain1.domain.ext

I have already placed the assetlinks.json file at both

subdomain1.domain.ext/.well-known/assetlinks.json

subdomain2.subdomain1.domain.ext/.well-known/assetlinks.json

But on installing the app using Android Studio, i don't see any hits to either files in the apache server's access logs.

Please note, i am using the release build variant which using the same keystore file which was used to generate the SHA256 fingerprint in assetlinks.json.

When i test with links such as

https://subdomain1.domain.ext/path1/subpath1/

https://subdomain2.subdomain1.domain.ext/path2

Only the lower one launches the app, the former one simply opens in browser. So it seems that the final line of code (second host) is set for app link.

Qn 1. How to open two paths/links on separate hosts with the same activity? In my case, how can i make the first link also open the app?

Qn 2. I want to restrict opening some links in the app, i tried this regex as path pattern, it did not work. I know its a glob, is there someway this can be done?

android:pathPattern="^(?!foo|bar)..*$"

to exclude link paths that begin with foo and bar but allow others.

i want to open

example.com in web browser
example.com/test in web browser
example.com/test/temp in web browser
example.com/{anything else} in the app

I read the other stackoverflow posts about this:

But i dont have any query parameters. My situation is pretty much similar to that described in Android Deep linking omit certain url

Qn 3. Is it mandatory that for the activity where the intent filter for such app links (web link launching app) is defined contain action=MAIN and category=LAUNCHER?

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

Looking forward to some help. It seems so foolish of the Android team to not allow exclude paths, should have learnt from Apple's server-side file which uses a simple NOT clause.

In case it helps, this is the java code in the onCreate() method of LaunchActivity

public class LaunchActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splashscreen);
        Intent intent = getIntent();
        if (intent != null) {
            Uri target = intent.getData();
            if (target != null && target.isHierarchical()) {
                String goal = intent.getDataString();
                List<String> params = target.getPathSegments();
                Log.d(TAG, "app uri : " + goal);
                Log.d(TAG, "app link : " + target.toString());
                Log.d(TAG, "path segments : " + params);
                if (target.toString().startsWith(MON_DOM)) {
                    if (searchString(AppLinksUtil.TARGET_URLS, target.toString())) {
                        Log.d(TAG, "Open TARGET Link in APP");
                    } else {
                        Log.d(TAG, "Open Excluded Link in Browser");
                        openLinkInChrome(LaunchActivity.this, goal);
                        finish();
                    }
                } else if (target.toString().startsWith(ENQ_DOM)) {
                    Log.d(TAG, "Exclude List : " + AppLinksUtil.ENQ_EXCLUDE_URLS);
                    if (searchString(AppLinksUtil.EXCLUDE_URLS, target.toString())) {
                        Log.d(TAG, "Open Excluded Link in Browser");
                        openLinkInChrome(LaunchActivity.this, goal);
                        finish();
                    } else {
                        Log.d(TAG, "Open Link in APP");
                    }
                }
            }
        } else {
            Log.d(TAG, "no intent");
        }
        appFlow();
    }
    ...
    public void openLinkInChrome(final Activity activity, String link) {
        Log.d(TAG, "Opening " + link + " in web browser");
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setPackage("com.android.chrome");
        try {
            activity.startActivity(intent);
        } catch (ActivityNotFoundException ex) {
            intent.setPackage(null);
            activity.startActivity(intent);
        }
    }
    public static boolean searchString(String[] arr, String targetValue) {
        for (String s : arr) {
            if (targetValue.startsWith(s))
                return true;
        }
        return false;
    }
}

Solution

  • On trying some code changes i realized that

    For 1, There is NO wildcard option for subdomains of same domain, multiple domains are supported, listing them one after the other with correct pathPatterns fixed the problems i was facing.

    AndroidManifest.xml

    <activity android:name="MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http" />
        <data android:host="subdomain1.example1.com" />
        <data android:host="subdomain2.example1.com" />
        <data android:host="subdomain3.example2.com" />
        <data android:path="/onlythis" />
        <data android:pathPrefix="/starter" />
        <data android:pathPattern="/prefix.*" />
        <data android:pathPattern="/.*suffix" />
    </intent-filter>
    </activity>
    

    For 2, Exclusion of Paths is not possible yet, Google must learn from Apple.

    For 3, any activity can be linked, not just the MAIN/LAUNCHER one.

    action=MAIN and category=LAUNCHER is not required for app linking code, action=VIEW along with category=DEFAULT and BROWSABLE are required and sufficient.

    Therefore, different domains/subdomains can launch different activities as long as default and browsable are defined along with action=VIEW