Tom Insam

Working around Android M Intent Resolving

In Intent Resolving in Android M, Said Tahsin Dane points out a change in Android Marshmallow to the intent querying mechanism. Specifically, when using queryIntentActivities,

If there is a domain verified application, [queryIntentActivities] does not return anything else. MATCH_ALL flag removes some system filters but only if there is no verified application.

This makes a lot of sense if I’m an application wanting to open a link to some other app and I want the system to just Do The Right Thing. But it led to problems with one aspect of the Eventbrite app.

Sometimes I want to be able to open the event the user is currently looking at in a web browser. I was doing this by constructing the URL to the web version, asking the system for everything that could handle it, then removing the Eventbrite app from the list and presenting the remaining intents (presumably the user’s web browser(s)) in a chooser. But when I added validated App Links, the list of “remaining intents” was empty - I had only been offered myself in list of possible handlers for the URL.

I’m working around this by building the intents list in 2 stages. First, I ask for a list of apps that can handle a “normal” URL. Then when I build the Intent list I’ll substitute the “real” URL:

// This URI is handled by the Android M "App Links" system, and if I
// try to open it the only intent the system will offer me is the
// Eventbrite app.
Uri realUri = Uri.parse("https://www.eventbrite.com/e/17811426456");

// We'll ask the system to open a generic URL, rather than the deep-link
// capable one we actually want.
Uri fakeUri = Uri.parse("http://www.eventbrite.com");

Intent browserIntent = new Intent(Intent.ACTION_VIEW, fakeUri);
PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(browserIntent, 0);

// Loop through everything the system gives us, and remove the current
// app (the whole point here is to open the link in something else).
final List<Intent> targetIntents = new ArrayList<>(activities.size());
for (ResolveInfo currentInfo : activities) {
    String packageName = currentInfo.activityInfo.packageName;
    if (!packageName.contains("com.eventbrite")) {
        // Build an intent pointing to the found package, but
        // this intent will contain the _real_ url.
        Intent intent = new Intent(Intent.ACTION_VIEW, realUri);
        intent.setPackage(packageName);
        intent.setData(realUri);
        targetIntents.add(intent);
    }
}

// Now present the user with the list of apps we have found (this chooser
// is smart enough to just open a single option directly, so we don't need
// to handle that case).
Intent chooserIntent = Intent.createChooser(targetIntents.remove(0), "");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
targetIntents.toArray(new Parcelable[targetIntents.size()]));
startActivity(chooserIntent);

Now I can offer to open the current view in the user’s web browser, but still retain the Eventbrite app as the default handler for Eventbrite events when followed from elsewhere.