Just An Application

February 25, 2011

The Android Intent APIs: Part Eight – Android 3.0 Additions

Given the amount you could already do to and with a common or garden Intent it is difficult to see what else they could have added in the Honeycomb release but they have managed to come up with a few things.

1. The Intent Class

The Intent class has acquired two rather specialized factory methods.

The method

    public static Intent makeMainActivity(ComponentName mainActivity)

will return an Intent which can be used to launch an Application with the named main Activity.

For example,

    Intent i = Intent.makeMainActivity(
                          new ComponentName(
                                  "xper.honeycomb",
                                  "xper.honycomb.XperActivity"));
        
    System.out.println("action     == " + i.getAction());
    System.out.println("categories == " + i.getCategories());
    System.out.println("component  == " + i.getComponent());
    System.out.println("flags      == 0x" + Integer.toHexString(i.getFlags()));

prints

    action     == android.intent.action.MAIN
    categories == [android.intent.category.LAUNCHER]
    component  == ComponentInfo{xper.honeycomb/xper.honycomb.XperActivity}
    flags      == 0x0

The method

    public static Intent makeRestartActivityTask(ComponentName mainActivity)

will return an Intent to re-launch an Application with the named main Activity.

For example,

    Intent j = Intent.makeRestartActivityTask(
                          new ComponentName(
                                  "xper.honeycomb", 
                                  "xper.honycomb.XperActivity"));
        
    System.out.println("action     == " + j.getAction());
    System.out.println("categories == " + j.getCategories());
    System.out.println("component  == " + j.getComponent());
    System.out.println("flags      == 0x" + Integer.toHexString(j.getFlags()));

prints

    action     == android.intent.action.MAIN
    categories == [android.intent.category.LAUNCHER]
    component  == ComponentInfo{xper.honeycomb/xper.honycomb.XperActivity}
    flags      == 0x10008000

As can be seen the difference between the two methods is that the second one sets two flags,

    Intent.FLAG_ACTIVITY_NEW_TASK(0x10000000)

and

    Intent.FLAG_ACTIVITY_CLEAR_TASK(0x00008000))

Quite why it is necessary to add two brand new methods for this purpose is not that obvious.

2. Activities And Intents

The method

    public abstract void startActivities(Intent[] intents)

has been added to the class android.app.Content

An implementation of this method will effectively start a stack of Activities with the Activity at the bottom corresponding to the first Intent in the array and the Activity at the top corresponding to the last Intent in the array. When started in this way an Activity is not created until it is actually accessed by the User

For example, if an Application defines the following Activities

    <activity 
        android:name  = "Foo" 
        android:label = "Foo">
        <intent-filter>
            <action 
                android:name = "xper.intent.FOO_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
    <activity 
        android:name  = "Bar" 
        android:label = "Bar">
        <intent-filter>
            <action 
                android:name = "xper.intent.BAR_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>
    <activity 
        android:name  = "Baz" 
        android:label = "Baz">
        <intent-filter>
            <action 
                android:name = "xper.intent.BAZ_INTENT"/>
            <category 
                android:name = "android.intent.category.DEFAULT"/>
        </intent-filter>
    </activity>

and it executes

    startActivities(
        new Intent[]
        {
            new Intent("xper.intent.FOO_INTENT"),
            new Intent("xper.intent.BAR_INTENT"),
            new Intent("xper.intent.BAZ_INTENT")
        });

then an instance of Baz is created and made the current Activity.

If Baz finishes then an instance of Bar is created and is made the current Activity.

If Bar finishes then an instance of Foo is created and is made the current Activity.

The method documentation states

This method throws ActivityNotFoundException if there was no Activity found for any given Intent. In this case the state of the activity stack is undefined (some Intents in the list may be on it, some not), so you probably want to avoid such situations.

which is not that helpful.

What currently appears to happen in practice is that as long as the ActivityNotFoundException is caught all the Activities corresponding to the Intents before the one that cannot be resolved are started.

There is no mention of whether this method plays nicely with things like the Intent.FLAG_ACTIVITY_CLEAR_TOP flag and other of that ilk should you be sufficiently adventurous to set them in any of the given Intents.

3. Broadcast Intents

The BroadcastReceiver class has acquired an inner class

   PendingResult

and a new method

   public final PendingResult goAsync()

This method makes it possible for a BroadcastReceiver to complete the handling of a broadcast Intent after its onReceive() method has returned. This is done by handing off the returned PendingResult instance to a different thread which can then do the necessary work before using the PendingResult instance to complete the broadcast.

The PendingResult class defines almost the same set of methods for manipulating the broadcast Intent as the BroadcastReceiver class plus the additional method

    public final void finish()

which is used to indicate that the handling of the broadcast Intent has been completed.

The only documentation on this feature appears to be the finish() method documentation, and the PendingResult class
documentation.

The BroadcastReceiver class documentation is otherwise unchanged.

For example

A BroadcastReceiver object is only valid for the duration of the call to onReceive(Context, Intent). Once your code returns from this function, the system considers the object to be finished and no longer active.

which is no longer necessarily true, which is a bit unfortunate.

In the absence of any additional documentation some experiments reveal the following

  • The finish() method works for both dynamically and statically registered BroadcastReceivers.

  • The finish() method works for

    broadcast Intents.

  • The process running a dynamically registered BroadcastReceiver will be killed if the finish() method is not called within approximately ten seconds of the call to the goAsync() method if it is an ordered broadcast, but not otherwise.

  • The process running a statically registered BroadcastReceiver will be killed if the finish() method is not called within approximately ten seconds of the call to the goAsync() method irrespective of the type of the broadcast.

Given the upper limit on the amount of time which can elapse whilest a broadcast Intent is being handled asynchronously it is not clear how useful this feature actually. Presumably somebody out there needs it for something. Either that or somebody added it for a bet.

4. IntentSenders And PendingIntents

The PendingIntent class has acquired a fourth factory method

    public static PendingIntent getActivities(Context context, int requestCode, Intent[] intents, int flags)

Invoking the resulting IntentSender/PendingIntent is equivalent to calling an implementation of the Context.startActivities()
method.

The documentation for this method is a bit confusing. On the one hand it states (emphasis added)

The first Intent in the array is taken as the primary key for the PendingIntent, like the single Intent given to getActivity(Context, int, Intent, int).

and on the other (emphasis added again)

The last intent in the array represents the key for the PendingIntent. In other words, it is the significant element for matching (as done with the single intent given to getActivity(Context, int, Intent, int), its content will be the subject of replacement by send(Context, int, Intent) and FLAG_UPDATE_CURRENT, etc. This is because it is the most specific of the supplied intents, and the UI the user actually sees when the intents are started.

On the basis of some experiments (currently the source code for this is not available) it is the second version which is correct.


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

Advertisements

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.

Create a free website or blog at WordPress.com.

%d bloggers like this: