URI Matching in Android’s IntentFilters

This article is part 5 of 11 in the series Android Development

If you’ve done any work on Android that involves integrating apps, you’ll have come across the concept of IntentFilters, too. Put simply, IntentFilters are a means to specify which intents an app’s Activity responds to, so that Android filters out unlikely candidates early.

What’s nifty is that IntentFilters can be used to specify not only what Intent name to react to, but — to a degree — also what data to react to. As this data comes in the form of an URI, you essentially have a means of reacting to URI clicks1.

There’s a problem with that, though.

Let’s first cover a little more detail on what happens when you open a URI the standard way: you create an intent with the VIEW action, add the URI to view, and fire it off:

Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.unwesen.de"));
startActivity(i);

Android will then go through the IntentFilters for all Activities in all installed apps, and find the ones that are registered for the particular action and URI. To do so, apps will specify the URI data parts they’re interested in, e.g. like this:

<activity android:name=".TestActivity">
  <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" />
  </intent-filter>
</activity>

In this particular example, the TestActivity registers an interest in all URIs with the http scheme.

But you can be more flexible, and match other parts of the URIs, too. You could, for example, capture all clicks on links to the unwesen.de domain, or capture clicks on parts of a path, etc. It’s pretty cool, overall.

Here’s the problem, and I quote from the documentation:

These attributes are optional, but also mutually dependent: If a scheme is not specified for the intent filter, all the other URI attributes are ignored. If a host is not specified for the filter, the port attribute and all the path attributes are ignored.

The first observation I wish to make on this design decision is that, in a sense, it’s pretty damn good. By making the attributes depend on each other, and specifically having more specific attributes depend on the existence of less specific attributes, checks for a complete match can be performed in a hierarchical fashion, much like walking down a tree of candidates. That’ll make finding matches roughly O(log(N)) rather than O(N). Great stuff!

Unfortunately, while this is a good engineering decision, it’s also demonstrating a complete lack of understanding what URIs are actually supposed to look like. There are many URI schemes, and while the most popular are also URLs, there are also plenty of URNs out there. What’s more, not all URLs have the same components like host names2 or paths.

In the end, Android’s URI matching ends up only working for a very limited subset of URIs out there. I’m not opposed to the engineering decision here, I’m opposed to the terminology: the term URI should hardly be mentioned in the context of IntentFilters.

Where this becomes a problem is that I recently had to match various URIs via IntentFilters — so yes, there’s at least one real-world usage of all of this. The URIs in question were perfectly valid, except they did not conform to Android’s expectations. Instead, they had this form:

scheme:///path?parameters

In other words, there was no authority section, but I was supposed to match different paths to different Activities. This isn’t a particularly uncommon scenario: many apps define non-standardized URI schemes that they respond to, and few of them have any use for an authority section. Most of them have use for matching path parts to Activities, though.

So how to get around this? Pretty simple, actually, if rather hackish.

  1. Technically, that’s not correct: while most apps will use a predictable Intent to launch the browser shipped with Android, and this is certainly the recommended way of doing so, there can be no guarantee it actually happens. Apps can just use an internal WebView, much as you’d like use on iOS. []
  2. Authorities. []