Monday, December 5, 2016

Understanding Intent Filter with Examples

This article discusses the rules of intent via a comprehensive list of examples and shows an simple application to test intent filter behavior that readers can try with more interesting cases themselves. At the beginning and the end of the article, it also talks about why intent filter is important, the limitation of it and workarounds.

This article doesn’t address how to design intent filters and use them to construct a well connected application. It instead focuses on the technical details – the rules – of intent filter. If you often get lost wondering why your intent filter doesn’t work the way you think it should, then it’s likely this article can answer your question.

This article is useful to you, if you

  • Can write basic intent filters but often run into cases where the actual behavior of the intent filter is different from what is originally intended.
  • Understanding the basic rules of intent filter but want to know the most subtle details and exceptions.

This article is not for useful to you, if you

  • Don’t know the basics of intent filter yet. Please refer to the Android documentation before reading ahead.
  • Want to know how to design a well connected application or suites of application using intent and intent filters.

Also, since intent filter only works with implicit intent, all intent mentioned in the examples are assumed to be implicit by default unless otherwise noted. There is little discussion about explicit intent in this article. For difference between implicit and explicit and which to choose in different scenario, refer to the Android documentation.

A Simple Intent Test App

To facilitate creating examples and testing various cases quickly, I’ll first introduce a simple app that I wrote. This app consists of two Activities

  • An IntentTestActivity that is a simple dialog for specifying an arbitrary intent and send it. IntentTestActivity can specify the action, category and data of an intent, but not any intent extras. However, intent extras plays no role during intent resolution so we really don’t care about it.
  • An IntentTargetActivity that indicates whether the intent has came through. The Activity display nothing more than a string of Hello, you reached it here!. What is more interesting is that this Activity defines an intent filter.

By changing the intent filter of IntentTargetActivity and altering the input to IntentTestActivity, we can quickly tests and examine various examples.

IntentTestActivity

Here is the class definition and layout of IntentTestActivity

public class IntentTestActivity extends AppCompatActivity {
  private static String TAG =
      IntentTestActivity.class.getSimpleName();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_intent_test);

    configViews();
  }

  private void configViews() {
    Button sendButton = (Button) findViewById(R.id.btnSend);
    sendButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Intent intent = getIntentFromInput();
        Log.e(TAG, "Intent: \n" + intent.toString());

        ComponentName resolvedComponentName =
            intent.resolveActivity(getPackageManager());
        if (resolvedComponentName != null) {
          Toast.makeText(
              IntentTestActivity.this,
              "Intent resolved to " + resolvedComponentName,
              Toast.LENGTH_LONG).show();
          startActivity(intent);
        } else {
          Toast.makeText(
              IntentTestActivity.this,
              "Intent can't resolve.",
              Toast.LENGTH_LONG).show();
        }
      }
    });
  }

  private Intent getIntentFromInput() {
    String action = getActionString();
    List<String> categories = getCategories();
    String dataType = getDataType();
    Uri uri = getDataUri();

    Intent intent = new Intent();
    if (action != null) {
      intent.setAction(action);
    }
    for (String category : categories) {
      intent.addCategory(category);
    }
    if (dataType != null && uri != null) {
      intent.setDataAndType(uri, dataType);
    } else if (uri != null) {
      intent.setData(uri);
    } else if (dataType != null) {
      intent.setType(dataType);
    }

    return intent;
  }

  private String getActionString() {
    String action = ((EditText)findViewById(R.id.etAction))
        .getText().toString();
    return action.isEmpty() ? null : "android.intent.action."
        + action.toUpperCase();
  }

  private List<String> getCategories() {
    String categoriesList = ((EditText)findViewById(R.id.etCategory))
        .getText().toString();

    if (categoriesList.isEmpty()) {
      return new ArrayList<>();
    }

    String[] categories = categoriesList.split(",");

    List<String> ret = new ArrayList<>();
    for (String category : categories) {
      ret.add("android.intent.category." + category.toUpperCase());
    }
    return ret;
  }

  private String getDataType() {
    String type = ((EditText) findViewById(R.id.etType))
        .getText().toString();
    return type.isEmpty() ? null : type;
  }

  private Uri getDataUri() {
    String uri = ((EditText) findViewById(R.id.etUri))
        .getText().toString();
    return uri.isEmpty() ? null : Uri.parse(uri);
  }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.xiaolong.intenttest.IntentTestActivity">

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="Action"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:scrollHorizontally="true" />
            <EditText
                android:id="@+id/etAction"
                android:layout_width="200dp"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="Category"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:scrollHorizontally="true" />
            <EditText
                android:id="@+id/etCategory"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="Date Type"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:scrollHorizontally="true" />
            <EditText
                android:id="@+id/etType"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="Date URI"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:scrollHorizontally="true" />
            <EditText
                android:id="@+id/etUri"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <Button
                android:id="@+id/btnSend"
                android:text="Send Intent"/>
        </TableRow>
    </TableLayout>
</RelativeLayout>

The Activity is simply a dialog of 4 input fields (action, category, data type and data URL), plus a send button. When send button is hit, an Intent is assembled according to the given input and is sent to the Android system via a call to startActivity(). If the intent resolves to IntentTargetActivity, then that will be started.

Below is a screenshot of the IntentTestActivity. As you can see, the intent action is VIEW. The intent data MIME type is application/test-type1, which is obviously a contrived type. The intent data URI is http://www.test.com/intenttesting, which is obviously a contrived URI.

Imgur

In the examples later, instead of using a screenshot of the IntentTestActivity to show the configuration of an intent, it’s much more concise to just show the string representation of the intent constructed, i.e. the output of calling Intent.toString(). For the same intent as in the above screenshot, the string representation of the intent is:

Intent { act=android.intent.action.VIEW dat=http://www.test.com/intenttesting typ=application/test-type1 }

It’s not hard to see how parts of the string representation maps to components of the intent. Notice that in this example the category is note specified. If one or more categories is specified for the intent, it will shows as a list with the key cat in the string output.

IntentTargetActivity

The IntentTargetActivity is a dummy Activity whose sole purpose is to verify whether the intent send from IntentTestActivity can resolves to it or not. The only thing relevant is the Activity’s definition in the manifest file, which is shown below:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" android:pathPattern="/.*intent.*" />
        <data android:scheme="ftp" android:host="www.test1.com" />
    </intent-filter>
</activity>

Don’t worry if the syntax doesn’t make sense now. The different sub-tags are dissected later with concrete examples.

Examples

Action Test

Intent filter consists of three parts, action, category and data. The definition of each part defines a corresponding test, which an incoming intent as tested against. It’s worth to note that an intent must pass ALL three tests in order to pass the filter.

The first and simplest test is action test. An intent can specify zero or one action by callingIntent.setAction() or via constructor. An intent filter can specify zero or more actions via <action> tag. In the most common case, the incoming intent specifies one action and the intent filter specifies a list of – one or more – actions. In this case, the intent passes the filter if and only if the actions it specifies is in the list the filter specifies.

Example 1: Action Test Pass, Common Case
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.TEST1"/>
        <action android:name="android.intent.action.TEST2"/>

        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.TEST1 }

Result: Pass Imgur


Example 2: Action Test Fail, Common Case
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.TEST1"/>
        <action android:name="android.intent.action.TEST2"/>

        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.TEST3 }

Result: Fail Imgur


It’s legal for an intent filter to specify no action at all, but all intent fails the test with such a filter. For obvious reasons, an always-fail filter is hardly useful. However, in a remotely possible scenario, one can read the manifest at runtime including an always-fail intent filter. The following example shows this corner case.

Example 3: Always Fail Action Test
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.TEST1 }

Result: Fail Imgur


On the contrary, if the intent doesn’t specify an action, then it always passes the action test, as long as the intent filter specifies any action at all. The following example shows this case. Notice that the data type is also specified both in the intent and the intent filter. This is the minimal requirement for the case to work, since the intent will be entirely empty otherwise and such an empty intent can’t pass any filter

Example 4: Always Pass Action Test
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.TEST1"/>

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

        <data android:mimeType="application/test-type1" />
    </intent-filter>
</activity>

Intent:

Intent { typ=application/test-type1 }

Result: Pass Imgur

Category Test

The second test is category test. An intent can specify one or more categories by calling Intent.addCategory(). The system automatically add the DEFAULT category to an intent during intent resolution, so even if Intent.addCategory() is never called when building the intent, there is still at least one category, which is DEFAULT. The intent filter can specifies zero or more categories via <category> tag. For an intent to pass an intent filter, each category it specifies must also be specified by the filter, i.e. a is subset of check. This implies that if the filter doesn’t specify DEFAULT category, any intent will fail. So an Activity willing to accept implicit intent should always specify DEFAULT category.

Example 5: Category Test Pass, Common Case
Intent filter:

<activity android:name=".IntentTargetActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.TEST1"/>
                <category android:name="android.intent.category.TEST2"/>
            </intent-filter>
        </activity>

Intent:

Intent { act=android.intent.action.VIEW }

Result: Pass Imgur

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] }

Result: Pass Imgur

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1,android.intent.category.TEST2] }

Result: Pass Imgur


Example 6: Category Test Fail, Common Case
Intent filter:

<activity android:name=".IntentTargetActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.TEST1"/>
                <category android:name="android.intent.category.TEST2"/>
            </intent-filter>
        </activity>

Intent:

Intent { act=android.intent.action.VIEW cat=android.intent.category.TEST1, android.intent.category.TEST2, android.intent.category.TEST3] }

Result: Fail Imgur


The following example show that if DEFAULT category is missing from the intent filter, then no intent will pass, as explained above.

Example 7: Category Test Fail, Missing DEFAULT Category
Intent filter:

<activity android:name=".IntentTargetActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.TEST1"/>
                <category android:name="android.intent.category.TEST2"/>
            </intent-filter>
        </activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] }

Result: Fail Imgur

Data Test

The last and most complicated test is data test. The complexity comes from the fact that the data part of an intent consists of a data type and and a data URI, where the URI is further divided into several parts: scheme, host, port and path. And to make thing even worse, the path part of the URI can be specified in three different – and increasingly more general – ways: path, pathPrefix or pathPattern.

Due to the complexity, it’s useful to first understand how those different parts are composed to form a single, congruent test. First of all, data test is passed if and only if each part of data of the intent matched that of the intent filter, i.e. this is an AND relationship. For example, if the data type and data URI scheme matches but the URI host doesn’t, then the test still fail. Secondly, for each part of data, the intent filter can specify it more than once, and an intent matches if it matches any of the specification in the intent filter, i.e. this is an OR relationship.

It’s much easier to explain with a concrete example. Considering only the data part of the following intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" />
        <data android:scheme="https" />
        <data android:host="www.test.com" />
        <data android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

An intent passes the data test if and only if

  • its data type is application/test-type1
  • AND its data URI scheme is http OR https
  • AND its data URI host is www.test.com OR www.test1.com
  • AND its data URI path matches the pattern /.*intent.*

Notice that it’s permitted to combine the data tags when they define different attributes. So the above filter can be equivalently written as:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="https" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

However, when the intent filter is written this way, it seems to suggest that for the URI to match, it should either be http://www.test.com/.*intent.* or https://www.test1.com/.*intent.*. However, in reality, both http://www.test1.com/.*intent.* or https://www.test.com/.*intent.* matches too. To avoid such confusion, one should always write each attribute in a separate data tag, or only combine attributes that occur once. In all the following examples, I’ll stick to the convention to put each data attribute in its own data tag.

The next example shows that the location where a <data> tag attribute appears doesn’t matter. More specifically, even though the matching scheme, http and the matching host www.test1.com are in separate data tags, the match happens regardless.

Example 8: Data Test Success
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=http://www.test1.com/intensttest typ=application/test-type1 }

Result: Pass Imgur

Example 9: Data Test Fail, Data Type Mismatch
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=http://www.test1.com/intensttest typ=application/test-another-type }

Result: Fail Imgur

Example 10: Data Test Fail, Scheme Mismatch
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=https://www.test1.com/intenttest typ=application/test-type1 }

Result: Fail Imgur

Example 11: Data Test Fail, Host Mismatch
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=https://www.test2.com/intenttest typ=application/test-type1 }

Result: Fail Imgur

Example 12: Data Test Fail, Path Mismatch
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=http://www.test1.com/test typ=application/test-type1 }

Result: Fail Imgur

The example for port mismatch is not listed, but one can easily come up with the example on his own.


We just visited the most common case of data test, in which both data type and data URI are specified. However, there are quite a few special cases when either type, or URI, or both are absent. The following examples shows these special rules.

In the intent filter specifies only the data URI but no type, then an intent passes the data test only if its URI matches and it doesn’t specifies data type too. If the intent specifies data type, then it always fails.

Example 13: Data Test without Type Filter, Pass
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=http://www.test.com/intenttest }

Result: Pass Imgur

Example 14: Data Test without Type Filter, Fail
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:scheme="http" android:host="www.test.com" />
        <data android:scheme="ftp" android:host="www.test1.com" />
        <data android:pathPattern="/.*intent.*" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=http://www.test.com/intenttest typ=image/png }

Result: Fail Imgur

If the intent filter specifies data type but no URI, then an intent passes the data test if it’s type matches and it doesn’t specify URI also. The interesting special rule here is that if the intent specifies a file: or content: URI, it passes the test too. In other words, This rule reflects the expectation that components are able to get local data from a file or content provider.

Example 15: Data Test without URI Filter , Pass
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] typ=application/test-type1 }

Result: Pass Imgur

Example 16: Data Test without URI Filter , Pass with file: URI
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=file:///Document/resume typ=application/test-type1 }

Result: Pass Imgur

Example 17: Data Test without URI Filter , Fail with http: URI
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] dat=http://www.test.com/intenttest typ=application/test-type1 }

Result: Fail Imgur

One may conclude that omitting data URI filter has the same effect as defining scheme filter as file: or content:. This is not entirely true because when scheme filter is explicitly defined, the data URI of incoming intent can’t be null in order to pass. Since an intent without URI is not usually desired, it makes sense to always define the URI filter explicitly. Compare the following example with example 15 above.

Example 18: Comparison to Example 15, but Fail
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="application/test-type1" />
        <data android:scheme="file" />
        <data android:scheme="content" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.VIEW cat=[android.intent.category.TEST1] typ=application/test-type1 }

Result: Fail Imgur

Multiple Intent Filters

Rather complex intent filters can be constructed by above rules, but it doesn’t yet cover all useful cases. When an app looks for intent with specific action, category combinations, one intent filter is often not enough. Consider an image viewer app that can both view and edit png but can only view gif. This can not be specified with one intent filter: there is no way to specify, within a single intent filter, that it should accept EDIT of png but not gif.

That’s why it’s allowed for an app component to specify multiple intent filters. When multiple intent filters present, an intent passes the filter of that component if and only if it passes any one of the filters specified. For the image viewer app example, the intent filter should be the following:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <action android:name="android.intent.action.EDIT"/>

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

        <data android:mimeType="image/png" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="image/gif" />
    </intent-filter>
</activity>

Example 19: Multiple Filters, Pass
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <action android:name="android.intent.action.EDIT"/>

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

        <data android:mimeType="image/png" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="image/gif" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.EDIT dat=file:///Picture/image.png typ=image/png }

Result: Pass Imgur

Example 20: Multiple Filters, Fail
Intent filter:

<activity android:name=".IntentTargetActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <action android:name="android.intent.action.EDIT"/>

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

        <data android:mimeType="image/png" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

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

        <data android:mimeType="image/gif" />
    </intent-filter>
</activity>

Intent:

Intent { act=android.intent.action.EDIT dat=file:///Picture/image.gif typ=image/gif }

Result: Fail Imgur

That’s all the examples! If you followed all the way here, congratulations and hope you now have a better understanding of intent filters and some technical details.

Wednesday, April 13, 2016

Prototypal inheritance in Javascript

Overview

Javascript is not a classical language, meaning that it doesn’t have class. However, this doesn’t mean that object oriented programming is not supported in Javascript. In fact, all 3 major features of OOP, namely, inheritance, encapsulation and polymorphism, are all possible in Javascript via prototypal inheritance which is built into the language instead of class. Before detailing them further into this article, let’s first list how the 3 major features of OOP are supported in Javascript:

  • Inheritance in Javascript is supported by defining an object as the prototype of another object.
  • Encapsulation in Javascript is supported by its extensible object definition by defining related data and methods on the same prototype object, the latter possible because of first-class function.
  • Polymorphism in Javascript is supported by prototype chain lookup: an object’s behavior is determined at runtime by what is on its prototype chain.

However, the above is only one of the many possible ways to achieve OOP in Javascript, which is coined as pseudoclassical by Douglas Crockford. In his famous book, Javascript: The Good Parts, he listed other ways of doing OOP, such as pure prototypal, functional and building objects via parts, thanks to the flexibility of object in Javascript. On contrary, an object-oriented languages that relies on class to achieve OOP, such as Java and C++, as well as this way of doing OOP, are called classical.

Even though Javascript supports pure prototypal OOP and it seems to be the original design idea, pseudoclassical usage of Javascript is still the main stream. Many Javascript frameworks, such as Node.js and Google Closure are all promoting psuedoclassical pattern as the paradigm. There are even syntactical sugar that makes Javascript looks more classical. Therefore, this article focuses on the pseudoclassical usage of Javascript based on its prototypal nature. Without further ado, let’s go to some code examples.

Constructor Function

In Javascript, a constructor function roughly maps to class definition** in classical language such as Java. A constructor function is not different from any other function. The only difference is that a constructor function is called with the new keyword. Since there is no language feature to distinguish a constructor function from non-constructor function, it’s conventional to capitalize the first latter of a constructor function’s name, making it look even more like Java.

var Employee = function(firstName, lastName, id) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.id = id;
};

Employee.prototype.printInfo = function() {
    var name = this.firstName + " " + this.lastName;
    console.log("Name: " + name);
    console.log("Id: " + this.id);
};

var securityPerson = new Employee("Jack", "Lee", "0001");

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

The above code defines an Employee class which 3 fields assigned via the constructor. It also adds a printInfo method to the Employee class by defining it on the prototype object of the constructor function. Finally, we created an instance of Employee, securityPerson, and calls the printInfo method on it.

Note: The above paragraph uses classical notions such as class, field, instance, etc. which are not accurate in Javascript. However, because most people are most familiar with classical notions, we’ll keep using them to avoid the verbose differentiating explanation every time.

As shown in the above example, the prototype object of the constructor function, Employee.prototype above, is the place where methods of a class are defined. Where as fields are defined directly on the this object inside the constructor function.

When defining a functionin Javascript, the function object runs some code like this:

this.prototype = {constructor: this};

which creates an empty object as the prototype object of the constructor function. This object is not entirely empty as it has a constructor attribute pointing back to the constructor function. The constructor property is not very important, but it makes it easier to find the constructor of an object.

// This will print the constructor function, something like:
// function (firstName, lastName, id) {
//    this.firstName = firstName;
//    this.lastName = lastName;
//    this.id = id;
// }
console.log(securityPerson.constructor);

When a constructor function is called with the new keyword, the Javascript runtime does more then calling the function itself. The following is done in sequence under the hood:

  1. A new object is created that inherits from the constructor’s prototype object:

    var newObj = Object.create(Employee.prototype);
    

    Notice that the Object.create() method is used to assign the prototype to illustrate the process. In reality, the Javascript runtime does something lower level.

  2. The constructor is invoked with this bound to the new object:

    var ret = Employee.apply(newObj, "Jack", "Lee", "0001");
    
  3. The new object is returned. However, if the constructor function returns an object itself, then that is returned instead:

    var securityPerson = (typeof ret === 'object' && ret) || newOjb;
    

Prototype Chain

It’s mentioned in step 1 of how new works that the prototype of the newly created object is set to the prototype object of the constructor function. It’s crucial that we use two slightly different terms here. The prototype of an object is its prototype in the sense of prototypal inheritance. The prototype object of the constructor function is simply an attribute on the function object whose name is “prototype”. The prototype object is used in Javascript to enable the pseudoclassical new syntax, so that the newly created object has its prototype set to it. After an object is created by new, the prototype object of its constructor is no longer relevant.

What matters is the prototype of an object. The prototype is an internal property of an object and should not be modified, although modern browsers all implements the __proto__ pseudo-property that points to an object’s prototype. The __proto__ property should not be directly used by application code, as indicated by its __ prefix. Nonetheless, it’s useful for illustrating prototype chain machenism, so I’ll use it in code samples.

In Javascript, each object has an prototype, which is in turn an object. That object has its own prototype, which is in turn an object again. Following this chain of prototypes, it eventually leads to Object.prototype, which is the prototype object of the Object() constructor and thus the immediate prototype of all objects created via object literal or new Object(). The prototype of Object.prototype is null. For any object, its prototype, its prototype’s prototype, …, all the way to Object.prototype forms a chain called its prototype chain. The prototype chains of all objects in Javascript runtime forms a tree structure.

Note: The tree structure is guaranteed because there can not be loops in the prototype chain, a fundamental invariant enforced by Javascript runtime. One can try to force a prototype chain and the Javascript runtime will certainly throw:
var a = {}, b = {};
a.__proto__ = b;
b.__proto__ = a;
Try the above lines in Chrome results in Uncaught TypeError: Cyclic __proto__ value(…). By the way, the above code runs because Chrome happens to define __proto__ property and make it writable. One should never try to modify __proto__ in application code, as its behavior is only recently standardized and its usage is discouraged.

Here is an example showing the prototype chains:

var securityPerson = new Employee("Jack", "Lee", "0001");
var janitor = new Employee("Robert", "Douglas", "0002");

var printPrototypeChain = function(obj) {
    var chain = 'this';
    var prototype = obj.__proto__;
    while (true) {
        if (prototype === Employee.prototype) {
            chain += " -> Employee.prototype";
        } else if (prototype === Object.prototype) {
            chain += " -> Object.prototype";
        } else if (prototype === Function.prototype) {
            chain += " -> Function.prototype";
        } else if (prototype === null) {
            chain += " -> null";
            break;
        } else {
            chain += " -> ?";
        }
        prototype = prototype.__proto__;
    }
    console.log(chain);
};

// this -> Employee.prototype -> Object.prototype -> null
printPrototypeChain(securityPerson);

// this -> Employee.prototype -> Object.prototype -> null
printPrototypeChain(janitor);

// this -> Object.prototype -> null
printPrototypeChain({x:1, y:2});

// this -> Function.prototype -> Object.prototype -> null
printPrototypeChain(function() { return true; });

// this -> ? -> Object.prototype -> null
// The '?' stands for Array.prototype
printPrototypeChain(new Array(1, 2, 3));

Property Lookup

So why does prototype chain matters? The prototype chain is used when looking up a property on an object. For example, when evaluating an expression such as obj.someProperty, the Javascript rumtime will look for someProperty on obj itself, then following it’s prototype chain to try to find it on obj.__proto__, obj.__proto__.__proto__, until it reaches Object.prototype. If there is no someProperty on obj nor its prototype chain, undefined is returned. This allows an object to inherit its prototypes, thus achieving inheritance and code reuse.

Prototype chain is looked up when getting property but not when setting property. Otherwise, manipulating one object has the side effect of modifying the behavior of other objects with overlapping prototype chain. This is illustrated below:

var securityPerson = new Employee("Jack", "Lee", "0001");
var janitor = new Employee("Robert", "Douglas", "0002");

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

// Name: Robert Douglas
// Id: 0002
janitor.printInfo();

janitor.printInfo = function() {
    console.log("I am the janitor!");
};

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

// I am the janitor!
janitor.printInfo();

delete janitor.printInfo;

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

// Name: Robert Douglas
// Id: 0002
janitor.printInfo();

To summarize, getting a property is done via the prototype chain where as setting/deleting a property is always directly on the object itself.

An Example of Psuedoclassical OOP in Action

Finally, let’s see an example of psuedoclassical OOP in action, illustrating inheritence, encapsulation and polymorphism.

var Employee = function(firstName, lastName, id) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.id = id;
};

// Encapsulation is achieved by bundling related logic into the prototype object
// of the constructor function.
Employee.prototype.printInfo = function() {
    var name = this.firstName + " " + this.lastName;
    console.log("Name: " + name);
    console.log("Id: " + this.id);
    console.log("Annual income: " + this.getAnnualIncome());
};

Employee.prototype.getAnnualIncome = function() {
    // This is to mimic abstract method in Java.
    throw new Error("getAnnualIncome must be implemented by subclass.");
}

var Contractor = function(firstName, lastName, id, hourlyPay) {
    // The way to call super constructor in Javascript is to call it explicitly.
    Employee.call(this, firstName, lastName, id);
    this.hourlyPay = hourlyPay;
};
var tmpCtor = function() {};
tmpCtor.prototype = Employee.prototype;
Contractor.prototyye = new tmpCtor();

// This shows inheritance. The super method is reused with some subclass specific
// customization.
Contractor.prototype.printInfo = function() {
    // The way to call super method in Javascript is to call it explicitly.
    console.log("[Contractor]");
    Employee.prototype.printInfo.call(this);
}

// This shows polymorphism: when getAnnualIncome is called in
// Employee.prototype.printInfo, the exact behavior is determined by the subclass
// at runtime.
Contractor.prototype.getAnnualIncome = function() {
    return 12 * 20 * 8 * this.hourlyPay;
}

var FullTimeEmployee = function(firstName, lastName, id, salary) {
    // The way to call super constructor in Javascript is to call it explicitly.
    Employee.call(this, firstName, lastName, id);
    this.salary = salary;
}
tmpCtor.prototype = Employee.prototype;
FullTimeEmployee.prototyye = new tmpCtor();

FullTimeEmployee.prototype.printInfo = function() {
    console.log("[Full-time Employee]");
    // The way to call super method in Javascript is to call it explicitly.
    Employee.prototype.printInfo.call(this);
}

FullTimeEmployee.prototype.getAnnualIncome = function() {
    return 12 * this.salary;
}

var newMeetingRoomBuilder = new Contractor("Victor", "Johnson", "0003", 20);
// [Contractor]
// Name: Victor Johnson
// Id: 0003
// Annual income: 38400
newMeetingRoomBuilder.printInfo();

var systemAdmin = new FullTimeEmployee("Peter", "Huffman", "0004", 6000);
// [Full-time Employee]
// Name: Peter Huffman
// Id: 0004
// Annual income: 72000
systemAdmin.printInfo();

Thursday, December 31, 2015

Java 8 Resources

Java 8 has been released for more than a year yet I started to learn its new features just two days ago. One reason for my slow adoption is because I focuses mostly in Android programming last year where the new Java 8 features is not available. This is not strictly true though because one can potentially setup bytecode translation to use Java 8 in Android code (link).

During my search for Java 8 resources on the Internet, I found the following resources meritorious. Some of them are through and well-written, whereas others are perfect for building up basic familiarity in short time.

Processing Data with Java SE 8 Streams by Raoul-Gabriel Urma

Part 1
Part 2


5 Minutes Series by IDR Solutions Blog

Lambda
Method Reference
Default Method
Stream

Monday, November 16, 2015

Java Generics Note: Reification vs. Covariance

During my study of Java generics, two terms often comes up are reification and covariance.

Reification means the resolution of a instance's exact type at run time. A type is reifiable if JVM can tell the type given an instance at runtime, otherwise we say the type is unreifiable. All generic types are not reifiable, except when it's a unbounded wildcard type or raw type. This is because the Java compiler erases all type argument information with a procedure called type erasure. On the other hand, array types are reifiable, because the element type is available at run time.

Covariance is a property about deriving a set of types given a set of basic types. For instance, for any reference type T, List<T> and T[] are types of its own. Such a derived set of types is said to be covariant they inherit the type compatibility of the basic types. In this case, List<T> is not covariant, because List<Integer> is not a subtype of List<Object>

To conclude, generic types are unreifiable and incovariant, whereas array types are reifiable and covariant. One might ask, can we have other combination of this properties? Such as a set of types that are reifiable and incovariant, or unreifiable and covariant? To answer this question, one need to first understand an very important property that guides the design of strongly type language, such as Java, which is type-safety. For generic types, it means that the program shall not call List<Integer>'s add() method with a String type argument. For array, it means the program shall not call arr[i] = "foobar" if the type of arr is Integer[].

Generic types such as List<T> is unreifiable, meaning that the runtime can't tell List<String> from List<Integer>. Let's assume that List<T> is covariant, this means that List<String> and List<Integer> will both be a subtypes of List<Object>

List<Object> = new List<Integer>()
List<String> strList = (List&ltString>) intList; // Allowed as a down cast.
strList.add(100);
strList.get(0).length();

The issue happen at runtime. Since List<T> is unreifiable, JVM can't tell that line 3 is wrong, so no runtime exception will be thrown.However, line 4 will fail because Integer doesn't have a length() method (in reality a ClassCastException is thrown because Java compiler adds a cast before calling length()). Therefore, a unreifiable type have to also be incovariant to avoid issues like this.

Monday, March 10, 2014

Auto Margin in CSS

Auto margin is normally used to center an element horizontally. Basically, set margin to auto will cause margin-left and margin-right to be set by browser so that the element is centered horizontally in its containing block. An exception is that margin-left can not be less than 0 in this automatic setting, thus margin-left will not go negative if the width of the element is larger than the width of the containing block.

Auto margin can also be used to center element vertically. This is much rarer though. This happens when the element is absolutely positioned and when top and bottom are set. If that is the case, then the element will be centered both horizontally and vertically in its positioning parent. The difference is that margin-top can be negative but margin-left can not. In other words, when its positioning parent is narrower than itself in width, then it won't be centered horizontally, just as the discussion in previous paragraph. However, when its positioning parent is shorter than itself in height, it will still be centered vertically, by having a negative margin-top. This is useful when you want to have a image background that is always centered even if the view port size is small, as used in the second CSS trick in this article. Notice that the trick in the article has the flaw that when the window is very narrow in width, the background will no longer be centered due to non-negative margin-left discussed above, while it is still fine for the vertical centering no matter how short the window is.

Thursday, February 27, 2014

Ignoring Mouse Event on DOM Element Using CSS 'pointer-events' Property

Yesterday, I was trying to write my simple Javascript tooltip library that can handle customized feature such as tooltip width, orientation of appearance, and also support arbitrary div content. An example page is here.

Basically, the tooltip div element is positioned absolutely and it shall fade in and out when the trigger text's mouseenter and mouseleave events are fired.

The first difficulty is when the trigger text, say a span, span across multiple lines, such as example two. In CSS term, this span has more than one bounding boxes. This is not a problem if the trigger is a block element as a block element has exactly one bounding box. The desired effect is that the tooltip shall appear next to the bounding box that the mouse enters. This can be solved using the getClientRects method to get all the bounding rectangles of an inline element and decide which of them is entered by the mouse.

The second difficulty, which I want to record here, is as follows. Sometime, due to resizing of browser window, the tooltip will cover part of the trigger text. In extreme case, the tooltip will cover up the entire trigger. As a result, when the tooltip covers the trigger, mouseleave event will be fired on the trigger, even if the mouse is still "on" the trigger. Then the tooltip will fade out and the mouseenter will be fired again... Of course, due to browser event handle time, this will not repeat indefinitely, but it still causes an unpleasant glitch, as show in this JS fiddle. After having some discussion on StackOverflow, I finally found the solution, then "pointer-events" CSS property, which can be used to disable any pointer event on the tooltip and let the event go through to the trigger underneath it, as shown in this JS fiddle. This saved my day.

More info can be found here or here.

Sunday, February 23, 2014

Synchronize YouTube videos

Recently, I created a website to synchronize YouTube videos. The idea is that there is a master video and all other videos (called slave videos)  will be synchronized to the master one, in the sense that all playback action on the master video will be reflected on slave videos. This is useful when you want to watch a clip with friends on the other side of the Internet and want to keep your videos play synchronously, even if you pause or seek.

The master page send all playback actions to the web server for temporary storage using standard Ajax. The slave page, on the other hand, need to get these event updates from the server. This is a problem because in the HTTP paradigm, web clients are supposed to send queries to web server, not the other way around. The technology used here is called Comet, which is also called reverse Ajax. The idea of Comet is to allow server to push data to the client. There are multiple methods and framework to achieve Comet. The method I chose is called long polling. As its name suggests, the client need to poll the server continuously with high frequency to ask for update. This is normally done by send a new XMLHttpRequest at the end of the onload callback of the previous XMLHttpRequest. In my code, I add some rate limit so about 2 polling are executed per second. This seems to work but will be harder to scale as the server is being queried continuously, even if there is no event to update.