2

I'm building a basic web browser with the android webview component and recently added support for opening links in relevant external apps e.g. if you're on a page and click a youtube link, the youtube app is opened instead of navigating to the web page.

This works fine accept for when an app is freshly installed and you click on a link for the first time (I suspect my app isn't the default browser at this point). Then it always prompts if you want to open it in another app, even if the only other relevant apps are other browsers, which isn't a great user experience as the user is already in the browser they want to open the link in otherwise they wouldn't be using it.

So I need to be able to distinguish between a link that has a dedicated installed app (e.g. it's found a wikipedia app for wikipedia links) vs a link that there are no dedicated apps for and is suitable for any browser to open.

Here's the relevant code in MyWebViewClient.shouldOverrideUrlLoading()...

Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
if(intent!=null){
    PackageManager packageManager = context.getPackageManager();
    ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    if (info != null) {
        String suggestedPackageName = info.activityInfo.applicationInfo.packageName;
        String intentAction = intent.getAction();
        final boolean packageMatchesThisBrowser = (MY_PACKAGE_NAME).equals(suggestedPackageName);
        final boolean isUrlAttempt = UrlHelper.isUrlAttempt(url);
        final boolean areSuggestedAppsOnlyBrowsers = false; // ????
        final boolean canItBeOpenedInThisBrowser = isUrlAttempt;
        if(canItBeOpenedInThisBrowser && (packageMatchesThisBrowser || areSuggestedAppsOnlyBrowsers)){
            return false; // allow the url to load normally in the current web view
        }else {
            // Else we have a dedicated app link (e.g. tel://, whatsapp://, intent://) or app supported links like (e.g. https://youtube.com/...)
            context.startActivity(intent);
            return true; // Launched the activity successfully so block webview from loading
        }
    } else {
        // ...
    }
}
MeatPopsicle
  • 832
  • 13
  • 28
  • You can use PackageManager to see if the Intent can be handled, but it is going to be tricky. See [here](https://stackoverflow.com/questions/15407502/how-to-check-if-an-intent-can-be-handled-from-some-activity) – Mister Smith Jun 29 '20 at 20:24

2 Answers2

0

I came up with this, let me know if it works I'm not able to test it currently.

/**
 * Assumes that a browser is identified by having a IntentFilter match all http and https
 * protocols without matching either a host or path in the url.
 */
public boolean checkIfOtherBrowser(Context context, ResolveInfo info, Intent intent) {
    if (!intent.getScheme().equals("http") || !intent.getScheme().equals("https")) {
        return false;
    }

    IntentFilter filter = info.filter;
    if (filter != null) {
        int result = filter.match(
                context.getContentResolver(),
                intent,
                true,
                "somelogtag"
        );

        return (result & IntentFilter.MATCH_CATEGORY_SCHEME) > 0 && // Scheme was matched
                (result & IntentFilter.MATCH_CATEGORY_HOST) == 0 &&
                (result & IntentFilter.MATCH_CATEGORY_PATH) == 0; // But not for a specific host or path
    }

    return false;
}

Research resources used:

JensV
  • 3,997
  • 2
  • 19
  • 43
  • Interesting approach, I'll test it now – MeatPopsicle Jul 02 '20 at 12:07
  • 1
    It didn't work as provided BUT you definitely pointed me in the right direction and I think it's working as expected now. The key is the MATCH_CATEGORY_PATH to check if the dedicated address matched and queryIntentActivities to check for all possible activities. Thanks for the help! I've given you the bounty but will post the final solution separately. – MeatPopsicle Jul 02 '20 at 14:19
0

Thanks to JensV I think I came up with a working solution. At least it seems to be able to distinguish between dedicated apps for links (Instagram, wikipedia etc) vs general purpose apps like other browsers.

public boolean areResolvedActivitiesOnlyBrowsers(Context context, Intent uriIntent) {
    PackageManager packageManager = context.getPackageManager();
    List<ResolveInfo> resolvedActivityList;

    final String scheme = uriIntent.getScheme();
    if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
        return false;
    }

    // Find all activities that could open the URI intent
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        resolvedActivityList = packageManager.queryIntentActivities(uriIntent, PackageManager.MATCH_ALL);
    }else{
        resolvedActivityList = packageManager.queryIntentActivities(uriIntent, 0);
    }

    // Check each activity for a match with the path part of the URI, if
    for (ResolveInfo activityResolveInfo : resolvedActivityList) {
        final int match = activityResolveInfo.match;
        boolean matchesPath = (match & IntentFilter.MATCH_CATEGORY_PATH) > 0;
        if(matchesPath){
            return false;
        }
    }
    return true;
}
MeatPopsicle
  • 832
  • 13
  • 28