Just An Application

February 3, 2011

The Android Intent Based APIs: Part Four – Activities And Intents

1.0 Activities And Intents: A Simplified Model

The Android Application/Activity model is complex bordering on baroque. The following is not intended to be definitive, just sufficient to make it possible to explain how Intents are used in the context of Activities.

  • An Activity is an Application Component with an associated UI which is used to interact with the User.

  • An Application can comprise one or more Activities.

  • An Application can use one or more Activities to interact with the User but only one at a time.

  • An Application can use both Activities defined by itself and those defined by other Applications.

  • The Activities being used by an Application are usually grouped together in a task

  • The set of Activities in a task are represented as a stack.

  • The first Activity in the task is at the bottom of the stack.

  • The current Activity is at the top of the stack.

  • Activities start in response to Intents.

  • The Intents which an Activity will start in response to are specified using Intent Filters.

  • An Activity which is not started by another Activity will become the first Activity in a new task.

  • An Activity which is started by another Activity is by default in the same task as the Activity which started it.

  • When it finishes an Activity may return an Intent to the Activity which started it.

2.0 Associating Activities And Intents

The set of Intents an Activity can handle are specified by declaring intent-filter elements as children of the activity element used to declare the Activity itself in the Application’s manifest (the AndroidManifest.xml file).

When an Intent Filter is being declared in the context of an Activity the priority attribute cannot have a value greater than zero unless the Activity being declared is part of a System Application.

3.0 Starting Activities

3.1 Starting Activities And Intent Resolution

An Activity is started by specifying an Intent. Which Activity, if any, gets started for a given Intent is determined by the process of Intent resolution.

If an Intent explicitly specifies a component then the Intent resolves to that Application Component irrespective of whether it is actually an Activity or even whether it exists at all.

If an Intent does not specify a component then a search is made for all those Activities which satisfy two criteria

  1. one of their associated Intent Filters matches, as defined by the IntentFilter.match() method, the given Intent, and

  2. one of their associated Intent Filters specifies the android.intent.category.DEFAULT category

If the Intent specifies a package then the search is confined to the Activities in that Application package.

If more than one Activity is found then the Activity with the highest priority as defined by the associated Intent Filter(s) is chosen.

If all the Activities found have the same priority then the System will present the User with a choice and let them decide.

The methods used by the System to perform Intent resolution, or at least their functional equivalents, are available to Applications.

The Intent method

    public ComponentName resolveActivity(PackageManager pm)

will return the name of the Component to which the given Intent resolves, even if it is not an Activity or does not exist. In the case where the Intent resolves to multiple Activities with the same priority the name of the Activity which will be used to ask the User to choose will be returned.

The Intent method

    public ActivityInfo resolveActivityInfo(PackageManager pm, int flags)

behaves in the same way except that it will return null if the Intent resolves to a named Component which does not exist or is not an Activity.

These are both wrappers around a call to an implementation of the android.content.pm.PackageManager

    public abstract ResolveInfo resolveActivity (Intent intent, int flags)

method, with the flags argument either being, or including, the value specified by the class constant

   PackageManager.MATCH_DEFAULT_ONLY

The PackageManager class also defines the methid

    public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags)

An implementation of this method will return the set of all the Activities which are capable of being started in response to the given Intent.

3.2 Starting Activities And Intent Flags

The values which can be used as Intent flags are defined by the System as Intent class constants. The majority of them, those with the prefix FLAG_ACTIVITY_, are intended for use when an Intent is being used to start an Activity.

Some of the more interesting ones in this context are those that can be used for task management and Activity selection

3.2.1 FLAG_ACTIVITY_CLEAR_TOP

If the Intent resolves to an Activity in the current task the Activities above it on the stack are destroyed so that it is at the top of the stack, and it is re-used.

3.2.1.1 Example

There are four Activities in a task that have been started in the order One, Two, Three, Four.

Four starts an Activity using an Intent with the FLAG_ACTIVITY_CLEAR_TOP which resolves to Two.


3.2.2 FLAG_ACTIVITY_NEW_TASK

If the Intent resolves to an Activity which needs to be started it will be started in a new task.

3.2.3 FLAG_ACTIVITY_PREVIOUS_IS_TOP

For the purposes of deciding whether to re-use the top Activity the previous rather than the current Activity is considered to be top.[1]

3.2.4 FLAG_ACTIVITY_REORDER_TO_FRONT

If the Intent resolves to an Activity in the current task that Activity is moved to the top of the stack and re-used.

To be consistent with some of the other flags FLAG_ACTIVITY_REORDER_TO_TOP might have been a better name.

3.2.4.1 Example

There are four Activities in a task that have been started in the order One, Two, Three, Four.

Four starts an Activity using an Intent with the FLAG_ACTIVITY_REORDER_TO_FRONT which resolves to Two.

3.2.5 FLAG_ACTIVITY_SINGLE_TOP

If the Intent resolves to the Activity at the top of the current task stack it is re-used.

Note that in this context the top Activity is not necessarily the Activity at the top of the stack. See FLAG_ACTIVITY_PREVIOUS_IS_TOP.

3.3 Starting An Activity

An Activity can be started using an implementation of the android.content.Context

    public abstract void startActivity(Intent intent)

method.

If the given Intent cannot be resolved to an Activity or what it resolves to is not an Activity, or does not exist, an instance of the class

    android.content.ActivityNotFoundException 

will be thrown.

If the Application invoking the method has not been granted the permission necessary to start the Activity a SecurityException will be thrown.

If the method is not being invoked from an Activity the Intent must have the FLAG_ACTIVITY_NEW_TASK flag set.

3.3.1 The onNewIntent() Method

The effect of the startActivity() method is not always to start a new Activity.

In some cases an existing Activity will be re-used and its

    protected void onNewIntent(Intent intent)

method will be called with the starting Intent being passed as the intent argument.

For example, if the Activity SelfStart is declared as follows

    <activity 
        android:name  = "SelfStart" 
        android:label = "SelfStart">
        <intent-filter>
            <action 
                android:name = "xper.activity.ACTIVITY_SELF_START_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
            </intent-filter>
    </activity>

and its onNewIntent() method is defined to be

    protected void onNewIntent(Intent intent) 
    {
        System.out.println("SelfStart.onNewIntent()");
        System.out.println("\t" + intent);
        System.out.println("SelfStart.onNewIntent(): done");
    }

If, when it is the current Activity, it executes

    startActivity(
        new Intent(
                "xper.activity.ACTIVITY_SELF_START_INTENT").
            setFlags(
                Intent.FLAG_ACTIVITY_SINGLE_TOP));

then a new instance of SelfStart is not created and the onNewIntent() method prints

    SelfStart.onNewIntent()
        Intent { act=xper.activity.ACTIVITY_SELF_START_INTENT flg=0x20000000 cmp=xper.example.selfstart/.SelfStart }
    SelfStart.onNewIntent(): done

3.4 Starting An Activity And Getting A Result

The android.app.Activity method

    public void startActivityForResult(Intent intent, int requestCode)

will start an Activity in the same way as the startActivity() method above.

In addition if the requestCode argument is greater than or equal to zero then when the Activity that was started completes the caller’s

    protected void onActivityResult(int requestCode, int resultCode, Intent data)

method will be invoked.

The value of the requestCode argument will be the same as that passed to the orignal call to the startActivityForResult() method, so it can be used to identify the Activity from which the result originated.

In some cases the behaviour of the method is subtly different depending upon whether a result is requested or not.

For example, if the SelfStart Activity above executes the following

    startActivityForResult(
        new Intent(
                "xper.activity.ACTIVITY_SELF_START_INTENT").
            setFlags(
                 Intent.FLAG_ACTIVITY_SINGLE_TOP),
            -1);

the behaviour is the same as before.

However, if it executes

    startActivityForResult(
        new Intent(
                "xper.activity.ACTIVITY_SELF_START_INTENT").
            setFlags(
                Intent.FLAG_ACTIVITY_SINGLE_TOP),
        3);

then the Intent.FLAG_ACTIVITY_SINGLE_TOP flag is ignored and a second instance is created.

3.4.1 Returning A Result

An Activity can return a result code to the Activity that started it by calling the method

    public final void setResult(int resultCode)

before calling the finish() method.

It can return a result code and an Intent by calling the method

    public final void setResult(int resultCode, Intent data)

instead.

For example, assume two Activities Foo and Bar are part of the same Application.

Foo defines the following onActivityResult() method

    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
    {
        System.out.println("Foo.onActivityResult(...)");
        System.out.println("\trequestCode == " + requestCode);
        System.out.println("\tresultCode  == " + resultCode) ;
        System.out.println("\tdata        == " + data);
        System.out.println("ActivityFoo.onActivityResult(...) done");
    }

Bar is declared in the Application’s manifest as follows

    <activity 
        android:name = "Bar">
        <intent-filter>
            <action 
                android:name = "xper.activity.ACTIVITY_BAR_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>

Foo starts Bar

    startActivityForResult(
        new Intent(
                "xper.activity.ACTIVITY_BAR_INTENT"), 
        1);

At some point Bar returns a result

    setResult(117, new Intent("xper.activity.ACTIVITY_BAR_RESULT_INTENT"));
    finish();

and Foo’s onActivityResult() method prints

    Foo.onActivityResult(...)
        requestCode == 1
        resultCode  == 117
        data        == Intent { act=xper.activity.ACTIVITY_BAR_RESULT_INTENT }
    ActivityFoo.onActivityResult(...) done

A result can be returned from an Activity defined in one Application to an Activity defined in another as long as they are in the same task.

If the effect of a call to startActivityForResult() is to start the Activity in a new task then the callers’s Activity.onActivityResult() method will be invoked immediately with a resultCode argument of zero and data argument of null.

If an Activity started using the startActivityForResult() method does not call either of the above methods before it finishes then the Activity.onActivityResult() method will be invoked with a resultCode argument of zero and data argument of null.

3.3.2 “Forwarding” A Result

If Activity One starts Activity Two using the startActivityForResult() method and Activity Two then starts Activity Three, Activity Two can specify that the result from Activity Three be returned to Activity One rather to itself. This is done by setting the flag defined by the Intent class constant FLAG_ACTIVITY_FORWARD_RESULT in the Intent used to start the Activity.

For example, assume the Activity Baz is also part of the Application in the example above.

Baz is declared in the Application’s manifest as follows

    <activity 
        android:name = "Baz">
        <intent-filter>
            <action 
                android:name = "xper.activity.ACTIVITY_BAZ_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>

Foo starts Bar as before

    startActivityForResult(
        new Intent(
                "xper.activity.ACTIVITY_BAR_INTENT"), 
        1);

At some point Bar then starts Baz

    startActivity(
        new Intent(
                "xper.activity.ACTIVITY_BAZ_INTENT").
            setFlags(
                Intent.FLAG_ACTIVITY_FORWARD_RESULT));

At some point Baz returns a result

    setResult(211, new Intent("xper.activity.ACTIVITY_BAZ_RESULT_INTENT"));
    finish();

At some point Bar returns a result as before

    setResult(117, new Intent("xper.activity.ACTIVITY_BAR_RESULT_INTENT"));
    finish();

and Foo’s onActivityResult() method then prints

    Foo.onActivityResult(...)
        requestCode == 1
        resultCode  == 211
        data        == Intent { act=xper.activity.ACTIVITY_BAZ_RESULT_INTENT }
    ActivityFoo.onActivityResult(...) done

Note that Bar starts Baz using the startActivity() method. It is not possible to use the FLAG_ACTIVITY_FORWARD_RESULT flag in conjunction with the startActivityForResult(). Doing so will result in an instance of

    android.util.AndroidRuntimeException

being thrown.

Note also that the result from Baz is not returned to Foo until Bar itself has finished, and that the resultCode and data returned by Baz override those returned by Bar but the requestCode is unchanged.

4.0 Starting An Activity: Variations On A Theme

The android.app.Activity class provides a couple of more specialized ways to start Activities.

4.1 Starting An Activity Only If Needed

The method

    public boolean startActivityIfNeeded(Intent intent, int requestCode)

is analagous to the startActivityForResult() method.

If the requestCode argument is greater than or equal to zero it works in the same way. The difference between them is that this method will only start a new Activity if it is needed.

A new Activity is not needed if the Intent specified by the intent argument resolves to the caller of the method, that is to the currently running Activity, and there may only be one instance of that Activity. In this case a new Activity is not started.

In any other circumstances a new Activity will be started.

The method returns true if a new Activity is started, and false otherwise, in which case the caller is expected to handle the Intent directly.

For example, assume an Application declares the following Activities

   <activity 
        android:name  = ".Humber"
        android:label = "@string/app_name">
        <intent-filter>
            <action 
                android:name = "android.intent.action.MAIN" />
            <category 
                android:name = "android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity 
        android:name       = "Thames" 
        android:launchMode = "singleTop">
        <intent-filter>
            <action 
                android:name = "xper.activity.ACTIVITY_THAMES_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
    <activity 
        android:name = "Dover">
        <intent-filter>
            <action 
                android:name = "xper.activity.ACTIVITY_DOVER_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>

If Humber executes

    startActivityIfNeeded(
        new Intent(
                "xper.activity.ACTIVITY_THAMES_INTENT"), 
        -1);

the Activity Thames is started and the method returns true.

If Thames executes

    startActivityIfNeeded(
        new Intent(
                "xper.activity.ACTIVITY_THAMES_INTENT"), 
        -1);

it is not necessary to start a new instance of Thames and the method returns false.

If Thames executes

    startActivityIfNeeded(
        new Intent(
                "xper.activity.ACTIVITY_DOVER_INTENT"), 
        -1);

the Activity Dover is started and the method returns true.

If Dover executes

    startActivityIfNeeded(
        new Intent(
                "xper.activity.ACTIVITY_DOVER_INTENT"), 
        -1);

a second instance of the Dover Activity is started, because by default there may be multiple instances of it, and the method returns true.

Passing a requestCode greater than or equal to zero results in behaviour identical to startActivityForResult() with the same arguments.

For example, if Thames executes

    startActivityIfNeeded(
        new Intent(
                "xper.activity.ACTIVITY_THAMES_INTENT"), 
        2);

then a second instance of the Thames Activity is started, and the method returns true.

Given that in these circumstances it is apparently always necessary to start an Activity it is not clear why the method needs to take a second argument at all.

If an Activity needs to be started and one cannot be found an ActivityNotFoundException will be thrown.

4.2 Starting The Next Matching Method

If the method

    public boolean startNextMatchingActivity(Intent intent)

is called with the Intent used to start the current Activity, then the method will start the next Activity, if any, capable of being started in response to that Intent.

The Intent passed as the intent argument does not have to be identical to the one that started the current Activity, it can differ in the extras that it specifies, which means it is possible to pass data between Activities started in this mode.

The method returns true if an Activity has been started, in which case the current Activity should then call the finish() method, otherwise false.

In this context next means the Activity, if any, after the current Activity, in the list of Activities constructed during Intent resolution.

For example, if there are three Activities each declared in a different Application as follows

    <activity 
        android:name  = "Plymouth" 
        android:label = "Plymouth">
        <intent-filter 
            android:priority = "-2">
            <action 
                android:name = "xper.activity.CHANNEL_ACTIVITY_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
	
    ...
	
    <activity 
        android:name  = "Portland" 
        android:label = "Portland">
        <intent-filter 
            android:priority = "-1">
            <action 
                android:name = "xper.activity.CHANNEL_ACTIVITY_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
		
    ...
	
    <activity 
        android:name  = "Wight" 
        android:label = "Wight">
        <intent-filter>
            <action 
                android:name = "xper.activity.CHANNEL_ACTIVITY_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>

then, when used to start an Activity, an Intent with the action xper.activity.CHANNEL_ACTIVITY_INTENT will be resolved to these Activities in the order

  1. Wight

  2. Portland

  3. Plymouth

If some other Activity executes

    startActivity(
        new Intent(
                "xper.activity.CHANNEL_ACTIVITY_INTENT"))

then Wight will be started.

If Wight executes

    startNextMatchingActivity(
        new Intent(
                "xper.activity.CHANNEL_ACTIVITY_INTENT"))

the Portland will be started, and the method will return true.

If Portland executes

    startNextMatchingActivity(
        new Intent(
                "xper.activity.CHANNEL_ACTIVITY_INTENT"))

the Plymouth will be started, and the method will return true.

If Plymouth executes

    startNextMatchingActivity(
        new Intent(
                "xper.activity.CHANNEL_ACTIVITY_INTENT"))

the method will return false as there are no other Activities to start.

The behaviour is the same regardless of which Activity is started first.

If Plymouth is started first and it executes

    startNextMatchingActivity(
        new Intent(
                "xper.activity.CHANNEL_ACTIVITY_INTENT"))

the method will still return false. It will not wrap around to Wight.

5.0 Activity Implementation And Intent Access

An Activity can access the Intent which was used to start it by calling the method

    public Intent getIntent()

By default the Intent returned does not reflect any subsequent calls to the Activity’s onNewIntent() method.

However, an Activity can set the Intent which will be returned by calling the

    public void setIntent(Intent newIntent)

method.

6.0 Activity Intent Filters And Intent Categories

The System pre-defines a number of Intent categories. When these are specified in Intent Filters associated with Activities the System treats some of them as declarations which apply to those Activities or to the Applications that declare those Activities.

For example, if the category defined by the Intent() class constant

    CATEGORY_HOME

with the value

   "android.intent.category.HOME"

is specified, then the Application containing the declaration is treated by the System as the home Application and is started automatically when the System starts up. If there are multiple such Applications then the user may be offered the choice of which one to start as the home Application.

If the the category defined by the Intent() class constant

    CATEGORY_LAUNCHER

with the value

   "android.intent.category.LAUNCHER"

is specified, then the System will make it possible for the User to launch the Application containing the declaration via the UI in some way.

There is no way of knowing whether the overloading of Intent categories in this context was originally part of the design or whether its just something that somebody hit upon afterwards.

One slight problem with it is that it is not possible to know simply by looking at an Application’s manifest whether the Application or one or more of its Activities might have some special status or mode of behaviour, unless you are familiar with all the System pre-defined Intent categories and their semantics.

7.0 Starting A Specific Activity

As mentioned above if an Intent specifies a Component explicitly then the Intent resolves to that Component regardless of anything else specified by the Intent.

This makes it possible to use an Intent to start any Activity given only its ComponentName as long as the Application doing so has the necessary permission.

For example if the Forties Activity is declared in the xper.example.activity.forties Application package as follows

    <activity 
        android:name  = "Forties" 
        android:label = "Forties">
        <intent-filter>
            <action 
                android:name = "xper.activity.ACTIVITY_FORTIES_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>

then it can be started as follows

    startActivity(
        new Intent(
                "xper.activity.ACTIVITY_CROMARTY_INTENT").
            setClassName(
                "xper.example.activity.forties", 
                "xper.example.activity.forties.Forties"));

despite the fact that the xper.activity.ACTIVITY_CROMARTY_INTENT action does not appear in its Intent Filter.

8.0 “Anonymous” Activities

It is possible to specify an Activity without any Intent Filters, for example,

    <activity android:name="AnonymousActivity"/>

This Activity cannot be accessed from outside the Application which declared it, but from within that Application it can still be started by specifying it explicitly as described above, for example,

    startActivity(new Intent(this, AnonymousActivity.class));

If an Activity is internal to an Application and only ever needs to be started explicitly then this is actually useful functionality especially since it avoids the expense of full on Intent resolution.

Perhaps less usefully it is also possible to make an Activity without any Intent Filters accessible outside the Application which declared it by explicitly exporting it. For example,

    <activity 
        android:name     = "PublicAnonymousActivity"
        android:exported = "true"
    />

Notes

[1] It is not clear in what circumstances the FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent flag actually works.

The flag is handled in the com.android.server.am.ActivityStack method

    final int startActivityUncheckedLocked(
                  ActivityRecord r,
                  ActivityRecord sourceRecord, 
                  Uri[]          grantedUriPermissions,
                  int            grantedMode, 
                  boolean        onlyIfNeeded, 
                  boolean        doResume)

(lines 2039-2385).

The ActivityStack implementation can be found in the file

    $(ANDROID_SRC)/frameworks/base/services/java/com/android/server/am/ActivityStack.java

At line 2060 the notTop variable is initialized as follows (code slightly reformatted)

    ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;

At line 2259 the the top variable is initialized as follows

    ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);

This is immediately preceeded by the following comment(s) (lines 2256-2258)

    // If the activity being launched is the same as the one currently
    // at the top, then we need to check if it should only be launched
    // once.

The topRunningNonDelayedActivityLocked() method is defined as follows (lines 332-342)

    final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
        int i = mHistory.size()-1;
        while (i >= 0) {
            ActivityRecord r = (ActivityRecord)mHistory.get(i);
            if (!r.finishing && !r.delayedResume && r != notTop) {
                 return r;
            }
            i--;
        }
        return null;
    }

so the top Activity as returned by this method is the first ActivityRecord, starting from the back of the list of ActivityRecords held in the mHistory instance variable, which is not finishing, is not in the delayedResume state, and is not the ActivityRecord passed in as the notTop
argument.

If the notTop argument is null the expression

    r != notTop

is necessarily true.

If the notTop argument is not null then the expression

    r != notTop

may be false, in which case the current ActivityRecord r will be skipped.

Given the use of the != operator for the expression

    r != notTop

to be false, r and notTop have to be the same ActivityRecord object, and hence the ActivityRecord passed as the notTop argument must be in list of ActivityRecords held in mHistory.

This is necessarily true regardless of what the ActivityRecord passed as the notTop argument represents or what mHistory represents.

When invoked from the startActivityUncheckedLocked() topRunningNonDelayedActivityLocked() is passed the notTop variable.

When the FLAG_ACTIVITY_PREVIOUS_IS_TOP is set the notTop variable is the ActivityRecord passed as the argument r.

The method startActivityUncheckedLocked() is invoked by the

    final int startActivityLocked(
                 IApplicationThread caller,
                 Intent             intent, 
                 String             resolvedType,
                 Uri[]              grantedUriPermissions,
                 int                grantedMode, 
                 ActivityInfo       aInfo, 
                 IBinder            resultTo,
                 String             resultWho, 
                 int                requestCode,
                 int                callingPid, 
                 int                callingUid, 
                 boolean            onlyIfNeeded,
                 boolean            componentSpecified)

method [sic] (lines 1877-2037).

The ActivityRecord r is constructed (line 2002) but it is not added to the list of ActivityRecords held in mHistory before being passed to startActivityUncheckedLocked() (line 2035).

Nor, in the case when FLAG_ACTIVITY_PREVIOUS_IS_TOP is set, does it appear to have been added before being passed to topRunningNonDelayedActivityLocked() in the guise of the notTop variable, which implies that passing it cannot have any effect at all on the behaviour of that method.

This may explain why it does not seem to be possible to devise an example where setting the FLAG_ACTIVITY_PREVIOUS_IS_TOP flag has any effect.


Copyright (c) 2011 By Simon Lewis. All Rights Reserved.

Blog at WordPress.com.