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.

February 21, 2011

The Android Intent Based APIs: Part Seven – IntentSenders And PendingIntents

1. Model

  • An Intent Sender is an object which encapsulates
    • an Application Identity
    • an Intent
    • a function to perform with the Intent
  • Intent Senders are capable of performing four functions
    • starting an Activity
    • returning a result to an Activity
    • broadcasting an Intent
    • starting a Service
  • The function a given Intent Sender is capable of performing is fixed at the point it is constructed.
  • An Intent Sender is used to perform a function with an Intent as though it was being done by the Application whose identity it encapsulates.
  • An Intent Sender can be constructed such that it can only be used once
  • An Intent Sender can be cancelled after which it can no longer be used

2. The IntentSender Class

An Intent Sender is represented by an instance of the

    android.content.IntentSender

class.

2.1 Obtaining An IntentSender

An IntentSender instance cannot be constructed directly but one can be obtained from a PendingIntent instance.

2.2 Transfering An IntentSender Instance Between Applications

The IntentSender class implements the android.os.Parcelable interface so an IntentSender instance can be written to a Parcel using the canonical

    public void writeToParcel(Parcel out, int flags)

method.

Alternatively an IntentSender instance can be written to a Parcel using the method

    public static void writeIntentSenderOrNullToParcel(IntentSender sender, Parcel out)

and read from one using the corresponding method

    public static IntentSender readIntentSenderOrNullFromParcel(Parcel in)

Note

The documentation for the readIntentSenderOrNullFromParcel() method reads

Convenience function for reading either a Messenger or null pointer from a Parcel. You must have previously written the Messenger with writeIntentSenderOrNullToParcel(IntentSender, Parcel).

which is a bit confusing.

3. The PendingIntent Class

An instance of android.app.PendingIntent encapsulates an IntentSender.

A PendingIntent instance is a reference to an object which represents the PendingIntent and which exists independently of the Application which created it and those that use it. Multiple distinct PendingIntent instances in the same process can all refer to the same PendingIntent object.

The function to be performed by the IntentSender encapsulated by a given PendingIntent is determined by the way in which it is constructed.

3.1 Obtaining A PendingIntent: The Factory Methods

There are three static PendingIntentfactory methods which can be used to obtain a PendingIntent

    public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags)

    public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)
	
    public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags)

These return PendingIntent instances which can be used to

respectively.

Depending upon the given arguments each of these methods can either

  • create a new PendingIntent object,
  • modify an existing PendingIntent object,
  • modify an existing PendingIntent object and create a new PendingIntent object, or
  • do nothing

3.1.1 Factory Method Arguments

3.1.1.1 The Context

The context argument is used to identify the Application creating and/or modifying the PendingIntent object(s).

3.1.1.2 The RequestCode

According to the documentation the requestCode is not currently used. This is not strictly true. See here

3.1.1.3 The Intent

The intent argument is used to specify the Intent to encapsulate when creating a new PendingIntent object, or the values to use when modifying an existing PendingIntent object.

3.1.1.4 The Flags

The flags argument is dual-purpose.

It can be used to pass the following flags defined as PendingIntent class constants

  • FLAG_CANCEL_CURRENT
  • FLAG_NO_CREATE
  • FLAG_ONE_SHOT
  • FLAG_UPDATE_CURRENT

as required.

It can also also used to pass any of the flags defined by the Intent class constants which can be passed to the Intent.fillIn() method.

3.1.1.4.1 The PendingIntent Flags

3.1.1.4.1.1 FLAG_CANCEL_CURRENT

If this flag is specified and a matching PendingIntent exists then it is cancelled.

3.1.1.4.1.2 FLAG_NO_CREATE

If this flag is specified a new PendingIntent instance will not be created.

3.1.1.4.1.3 FLAG_ONE_SHOT

If this flag is specified and a new PendingIntent is created then it can only be used once.

3.1.1.4.1.4 FLAG_UPDATE_CURRENT

If this flag is specified and a matching PendingIntent exists then it will be updated with the …

3.1.1.4.2 The Intent Flags

The Intent sent by an IntentSender is a copy of the encapsulated Intent. This copy may be modified using the Intent.fillIn() method if an additional Intent is supplied by the user of the IntentSender. The Intent flags specified at this point are passed to the Intent.fillIn() method with the Intent supplied by the caller.

3.1.2 Factory Method PendingIntent Object Matching

When an Application calls one of the PendingIntent factory methods the arguments to that method will match an existing PendingIntent object, if and only if the following are all true

  1. the requestCode argument is equal to the requestCode specified when the PendingIntent was created
  2. the Intent specified by the intent argument is equal to the Intent specified when the PendingIntent was created,as defined by Intent.filterEquals() method
  3. the flags argument excluding the values of the
    • FLAG_CANCEL_CURRENT
    • FLAG_NO_CREATE, and
    • FLAG_UPDATE_CURRENT

    flags is equal to the flags specified when the PendingIntent was created, excluding the same flag values as above

  4. the function to perform is the same as that specified when the PendingIntent was created

3.1.2.1 Examples

If we assume that there are currently no existing PendingIntent objects and that an Activity in an Application executes the following

    Intent i1  = new Intent("xper.intentsender.intent.AN_INTENT");
    Intent i1a = new Intent("xper.intentsender.intent.AN_INTENT").putExtra("AN_EXTRA", "An extra value");
    Intent i2  = new Intent("xper.intentsender.intent.AN_INTENT").setType("type/subtype");

    // identical

   PendingIntent pi1 = PendingIntent.getActivity(this, 1, i1, 0);
   PendingIntent pi2 = PendingIntent.getActivity(this, 1, i1, 0);

   // different request codes

   PendingIntent pi3 = PendingIntent.getActivity(this, 2, i1, 0);

   // matching Intents

   PendingIntent pi4 = PendingIntent.getActivity(this, 1, i1a, 0);

   // different Intents

   PendingIntent pi5 = PendingIntent.getActivity(this, 1, i2, 0);

   // different flags one

   PendingIntent pi6 = PendingIntent.getActivity(this, 1, i1, PendingIntent.FLAG_ONE_SHOT);

   // different flags two

   PendingIntent pi7= PendingIntent.getActivity(this, 1, i1, Intent.FILL_IN_ACTION);

   // different functions

   PendingIntent pi8 = PendingIntent.getBroadcast(this, 1, i1, 0);

then

  • pi1 refers to a new PendingIntent object
  • pi2 refers to the same PendingIntent object as pi1
  • pi3 refers to a new PendingIntent object
  • pi4 refers to the same PendingIntent object as pi1 and pi2
  • pi5 refers to a new PendingIntent object
  • pi6 refers to a new PendingIntent object
  • pi7 refers to a new PendingIntent object
  • pi8 refers to a new PendingIntent object

3.1.3 Factory Method Return Values

3.1.3.1 Case One

If there is no existing PendingIntent object which matches and FLAG_NO_CREATE is specified a factory method will return null.

For example, if we assume there are currently no existing PendingIntent objects, that

    Intent i1 = new Intent("xper.intentsender.intent.AN_INTENT_ONE");
    Intent i2 = new Intent("xper.intentsender.intent.AN_INTENT_TWO");
    Intent i3 = new Intent("xper.intentsender.intent.AN_INTENT_THREE");

and that the following code is executed by an Activity within an Application, then

    PendingIntent.getBroadcast(this, 1, i1, PendingIntent.FLAG_NO_CREATE);

will return

    null

3.1.3.2 Case Two

If there is no existing PendingIntent object which matches and the FLAG_NO_CREATE is not specified then a factory method will return a PendingIntent instance which refers to a new PendingIntent object.

Continuing with the example, if the Activity now executes the following

    PendingIntent pi1 = PendingIntent.getBroadcast(this, 1, i1, 0);
    PendingIntent pi2 = PendingIntent.getBroadcast(this, 1, i2, 0);
    PendingIntent pi3 = PendingIntent.getBroadcast(this, 1, i3, 0);

then pi1, pi2, and pi3 all necessarily refer to new PendingIntent objects.

3.1.3.3 Case Three

If there is an existing PendingIntent object which matches and the FLAG_NO_CREATE is specified then a factory method will return a PendingIntent instance which refers to the existing PendingIntent object.

Continuing with the example again, if the Activity now executes the following

    PendingIntent pi4 = PendingIntent.getBroadcast(this, 1, i1, PendingIntent.FLAG_NO_CREATE);
    PendingIntent pi5 = PendingIntent.getBroadcast(this, 1, i2, PendingIntent.FLAG_CANCEL_CURRENT|PendingIntent.FLAG_NO_CREATE);
    PendingIntent pi6 = PendingIntent.getBroadcast(this, 1, i3, PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_NO_CREATE);

then

  • pi4 refers to the same PendingIntent object as pi1
  • pi5 refers to the same PendingIntent object as pi2 and it has been cancelled
  • pi6 refers to the same PendingIntent object as pi3

3.1.3.4 Case Four

If there is an existing PendingIntent object which matches and the FLAG_NO_CREATE is not specified then a factory method will either return a PendingIntent instance which refers to a new PendingIntent object, or a PendingIntent instance which refers to the existing PendingIntent object.

Continuing with the example again, if the Activity now executes the following

    PendingIntent pi7 = PendingIntent.getBroadcast(this, 1, i1, PendingIntent.FLAG_CANCEL_CURRENT);
    PendingIntent pi8 = PendingIntent.getBroadcast(this, 1, i3, PendingIntent.FLAG_UPDATE_CURRENT);
    PendingIntent pi9 = PendingIntent.getBroadcast(this, 1, i1, 0);

then

  • the PendingIntent referenced by pi1 and pi4 has been cancelled
  • pi7 refers to a new PendingIntent object
  • pi8 refers to the same PendingIntent object as pi3 and pi6
  • pi9 refers to the same PendingIntent object as pi7

3.2 Cancelling A PendingIntent

A PendingIntent object can be cancelled by calling the PendingIntent instance

    public void cancel()

method.

A SecurityException will be thrown if the calling Application did not create the PendingIntent object which is referenced by the PendingIntent instance.

3.3 Transfering A PendingIntent Instance Between Applications

The PendingIntent class implements the android.os.Parcelable interface so a PendingInstance instance can be written to a Parcel using the canonical

    public void writeToParcel (Parcel out, int flags)

method.

Alternatively a PendingIntent instance can be writen to a Parcel using the method

    public static void writePendingIntentOrNullToParcel(PendingIntent sender, Parcel out)

and read from one using the corresponding method

    public static PendingIntent readPendingIntentOrNullFromParcel(Parcel in)

Note

The documentation for the readPendingIntentOrNullFromParcel() method suffers from the same problem as the documentation for the corresponding method in the IntentSender class

4. Obtaining A PendingIntent Using Activity Methods

There is a single android.app.Activity method which can be used to obtain a PendingIntent object.

The method

    public PendingIntent createPendingResult(int requestCode, Intent data, int flags)

will return a PendingIntent instance which can be used to return results to the Activity which invoked this method via its Activity.onActivityResult() method

The requestCode, data and flags arguments are identical to the requestCode, intent and flags arguments of the PendingIntent factory methods.

5. Using An IntentSender

5.1 The sendIntent() Method

The function encapsulated by an IntentSender can be performed using its

    public void sendIntent(
                    Context                 context,
                    int                     code,
                    Intent                  intent,
                    IntentSender.OnFinished onFinished,
                    Handler                 handler)

method.

5.1.1 Arguments

5.1.1.1 The Context Argument

The context argument is only required if the intent argument is not null when it is used to obtain a ContentResolver.

5.1.1.2 The Code Argument

The semantics of the code argument are determined by the function being performed by the IntentSender.

5.1.1.3 The Intent Argument

The intent argument can be used to specify an Intent to use to create a modified version of the encapsulated Intent. The modification is performed using the Intent.fillIn() method. If this argument is not null then the parts of the Intent which will be used are specified by the set of Intent flags supplied when the IntentSender was created.

5.1.1.4 The OnFinished Argument

The onFinished argument can be used to specify a callback object which implements the IntentSender.OnFinished interface. This interface defines a single method

    public void onSendFinished(
                    IntentSender intentSender,
                    Intent       intent,
                    int          resultCode,
                    String       resultData,
                    Bundle       resultExtras)

which will be invoked when the function being performed by the IntentSender has been completed. The exact semantics are determined by the function being performed.

5.1.1.5 The Handler Argument

The handler argument can be used to specify the Handler in which the callback object, if any, specified by the onFinished argument should be run.

5.2 Using An IntentSender To Start An Activity

When an IntentSender is being used to start an Activity the code argument is not used and if the onFinished argument is not null
then the onSendFinished() method is invoked with values of

  • 0
  • null
  • null

for the

  • resultCode
  • resultData
  • resultExtras

arguments respectively.

If the IntentSender is no longer valid, for example, it has been cancelled, then an instance of the class IntentSender.SendException will be thrown.

5.3 Using An IntentSender To Return A Result To An Activity

When an IntentSender is being used to return a result to an Activity then code argument is passed as the resultCode argument of the Activity.onActivityResult() method.

If the onFinished argument is not null then the onSendFinished() method is invoked with values of

  • 0
  • null
  • null

for the

  • resultCode
  • resultData
  • resultExtras

arguments respectively.

If the IntentSender is no longer valid, for example, it has been cancelled, then an instance of the class IntentSender.SendException will be thrown.

5.4 Using An IntentSender To Broadcast An Intent

Although it is not clear from the documentation when an IntentSender is used to broadcast an Intent the onFinished argument controls how the broadcast is performed.

5.4.1 Normal Broadcast

If the onFinished argument is null then a normal broadcast is performed.

In this case the code argument is not used.

5.4.2 Ordered Broadcast

If the onFinished argument is not null then an ordered broadcast is performed.

In this case the code argument is equivalent to the initialCode argument of the long-form of the

Context.sendOrderedBroadcast() method.

When the ordered broadcast completes the onSendFinished() method is invoked with the results, with the

  • resultCode
  • resultData
  • resultExtras

arguments being the values which would be returned from calls to the corresponding accessor methods in a BroadcastReceiver.onReceive() method.

For example, if we create a PendingIntent instance as follows

    PendingIntent.getBroadcast(this, 3, new Intent("xper.example.ORDERED_BROADCAST_INTENT"),  0);

and we use the encapsulated IntentSender to run this example

    is.sendIntent(
           this,
           0,
           null,
           new ResultReceiver(),
           null)

where the ResultReceiver class is defined as

    public class ResultReceiver
                 implements
                     IntentSender.OnFinished
    {
        public void onSendFinished(
                        IntentSender intentSender,
                        Intent       intent,
                        int 		 resultCode,
                        String 		 resultData,
                        Bundle 		 resultExtras)
        {
            System.out.println("ResultReceiver.onSendFinished()");
            System.out.println(intentSender);
            System.out.println(intent);
            System.out.println("\tgetResultCode() => " + resultCode);
            System.out.println("\tgetResultData() => " + resultData);

            Bundle extras = resultExtras != null ? resultExtras : new Bundle();

            System.out.println("\tBegin Extras");
            for (String key : extras.keySet())
            {
                 System.out.print("\t\t");
                 System.out.print(key);
                 System.out.print("\t=> ");
                 System.out.println(extras.get(key));
            }
            System.out.println("\tEnd Extras");
            System.out.println("ResultReceiver.onSendFinished() done");
        }
    }

then when the broadcast completes the ResultReceiver.onSendFinished() method prints

    ResultReceiver.onSendFinished()
    IntentSender{4051f870: android.os.BinderProxy@4051f838}
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT }
        getResultCode() => 341
        getResultData() => null, Three, TwoB, TwoA, One
        Begin Extras
            Three.value	=> Three
            TwoB.value	=> TwoB
            TwoA.value	=> TwoA
            One.value	=> One
        End Extras
    ResultReceiver.onSendFinished() done

The output is not exactly the same as in the original example because the initialData cannot be specified.

5.5 Using An IntentSender To Start A Service

When an IntentSender is being used to start a Service the code argument is not used and if the onFinished argument is not null

then the onSendFinished() method is invoked with values of

  • 0
  • null
  • null

for the

  • resultCode
  • resultData
  • resultExtras

arguments respectively.

If the IntentSender is no longer valid, for example, it has been cancelled, then an instance of the class IntentSender.SendException will be thrown.

6. Using An IntentSender From An Activity

The android.app.Activity class defines two methods which can be used to perform the function encapsulated by an IntentSender.

    public void startIntentSender(
                    IntentSender intent,
                    Intent       fillInIntent,
                    int          flagsMask,
                    int          flagsValues,
                    int          extraFlags)

    public void startIntentSenderForResult(
                    IntentSender intent,
                    int          requestCode,
                    Intent       fillInIntent,
                    int          flagsMask,
                    int          flagsValues,
                    int          extraFlags)

They are intended to be analagous to the methods

    public void startActivity(Intent intent)

and

    public void startActivityForResult(Intent intent, int requestCode)

respectively, but despite this they can in fact be used with any IntentSender not just those which start Activities.

The arguments in common are the same for both methods.

The fillInIntent argument is equivalent to the intent argument of the IntentSender.sendIntent() method.

The flagsMask and flagsValues can be used to modify the flags of the Intent sent by the IntentSender. Effectively

    flags = ((originalFlags & ~flagsMask) | (flagValues & flagsMask))

In practice it is not done quite like this for security reasons. Note that this makes it possible to turn off flags which cannot be done using the Intent.fillIn() method.

The extraFlags argument is not used.

If the startIntentSenderForResult() is used to start an Activity then once it has finished, the calling Activity’s onActivityResult() method will be called as normal.

7. Using A PendingIntent

The PendingIntent class defines no less than five methods which can be used to invoke the encapsulated IntentSender

    public void send()

    public void send(int code)

    public void send(Context context, int code, Intent intent)

    public void send(int code, PendingIntent.OnFinished onFinished, Handler handler)

    public void send(
                    Context                  context,
                    int                      code,
                    Intent                   intent,
                    PendingIntent.OnFinished onFinished,
                    Handler                  handler)

Of these the first four are simply wrapper methods around the fifth, providing the appropriate defaults as necessary.

The behaviour of the fifth method is identical to that of the IntentSender.sendIntent() method.


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

February 17, 2011

The Android Intent Based APIs: Part Six – Broadcast Intents

As well as being used in conjunction with Activites and Services Intents can also be used as broadcast Intents.

1. The Broadcast Intent Model

  • Any Application can send a broadcast Intent.

  • To receive broadcast Intents an Application must register a BroadcastReceiver.

  • An Application can register multiple BroadcastReceivers.

  • An Application can register BroadcastReceivers statically and/or dynamically.

  • A BroadcastReceiver can be registered with an associated Intent Filter.

  • The Intent Filter is used to specify which broadcast Intents the BroadcastReceiver wishes to receive.

  • A normal broadcast Intent is sent asynchronously and the ordering of its delivery to the set of BroadcastReceivers eligible to receive it is undefined.

  • An ordered broadcast Intent is delivered sequentially to each member of the set of BroadcastReceivers eligible to receive it in the order defined by the priority of the associated IntentFilters.

  • An ordered broadcast Intent can have additional data associated with it in the form of

    • a code (an int),

    • data (a String), and

    • extras (a Bundle)

  • The initial values of the additional data can be specified by the sender of the ordered broadcast Intent

  • The current values of the additional data can be read and/or replaced by each BroadcastReceiver which receives the ordered broadcast Intent

  • Any BroadcastReceiver which receives an ordered broadcast Intent can stop the sending process

  • The sender of an ordered broadcast Intent can specify that it be notified when the broadcast has completed

  • The sender of an ordered broadcast Intent can access the final values of the additional data when it is notified that the broadcast has completed.

  • A broadcast Intent can be specified to be sticky in which case it will be retained by the system after it has been sent.

  • A sticky broadcast Intent can be removed after it has been sent.

  • BroadcastReceivers dynamically registered after a sticky broadcast Intent has been sent, and not subsequently removed, will still receive it if all other the criteria for receiving it have been met.

  • A sticky broadcast Intent can be retrieved at any time after it has been sent without registering a BroadcastReceiver.

  • An Application can specify a permission when sending a normal or ordered broadcast Intent.

  • A BroadcastReceiver cannot receive a normal or ordered broadcast Intent sent with an associated permission if the Application that registered the BroadcastReceiver has not been granted that permission.

  • An Application can specify a permission when registering a BroadcastReceiver.

  • A BroadcastReceiver registered with an associated permission cannot receive any normal or ordered broadcast Intent sent by an Application which has not been granted that permission.

When used in this way Intents are effectively Events and BroadcastReceivers are Event Handlers/Listeners.

2. The BroadcastReceiver Class

The class

    android.content.BroadcastReceiver

is abstract. To receive broadcast Intents a sub-class must be defined which implements the method

    public abstract void onReceive(Context context, Intent intent)

It is this method which is invoked when a broadcast Intent satisfies the criteria the BroadcastReceiver was registered with.

3. Registering Broadcast Receivers Statically

An Application can declare Broadcast Receivers in its manifest (the AndroidManifest.xml file), by defining one or more

    receiver

elements, as children of the

    application 

element.

3.1 The receiver Element

3.1.1 Attributes

3.1.1.1 The enabled Attribute

The enabled attribute is optional. If present its value can be either “true” or “false”

If the value is “true” then the System can create instances of the BroadcastReceiver and it can function normally.

If the value is “false” then the System cannot create instances of the BroadcastReceiver and it is not functional.

The default value is “true”.

If the Application itself is not enabled then the BroadcastReceiver is not functional.

3.1.1.2 The exported Attribute

The exported attribute is optional. If present its value can be either “true” or “false”.

If the value is “true” then the BroadcastReceiver can, if all other criteria are met, receive broadcast Intents from any Application.

If the value is “false” then the BroadcastReceiver can only receive broadcast Intents from the registering Application or other Applications with the same User Id even if the other specified criteria are met.

The default value is “true” if the registered BroadcastReceiver has one or more IntentFilters associated with it. Otherwise it is “false”.

3.1.1.3 The icon Attribute

The icon attribute is optional. If present it should specify a Drawable resource. This will be used by the System to visually identify the BroadcastReceiver to the User if necessary.

3.1.1.4 The label Attribute

The label attribute is optional. If present it should specify a String resource. This will be used by the System to identify the BroadcastReceiver to the User if necessary.

3.1.1.5 The name Attribute

The name attribute is mandatory. It specifies the name of the BroadcastReceiver class.

The documentation for this attribute states

This should be a fully qualified class name (such as, “com.example.project.ReportReceiver”). However, as a shorthand, if the first character of the name is a period (for example, “. ReportReceiver”), it is appended to the package name specified in the <manifest> element.

Which is true as far as it goes.

By default the Android ADT Eclipse plugin actually generates receiver elements that look like this

    <receiver android:name="XperBroadcastReceiver"></receiver>

The attribute value is simply the unqualified class name and this also works.

3.1.1.6 The permission Attribute

The permission attribute is optional. If present then the BroadcastReceiver cannot receive broadcast Intents sent by Applications which have not been granted the specified permission.

3.1.1.7 The process Attribute

The process attribute is optional. If present it specifies the process in which the Systen should create the BroadcastReceiver when necessary.

3.1.2 Child Elements

The receiver element can have two child elements

    intent-filter

and

    meta-data

Both elements are optional. If present both elements can occur multiple times.

4. Registering And Unregistering BroadcastReceivers Dynamically

4.1 Registration

A BroadcastReceiver can be registered dynamically using an implementation of the android.content.Context

    public abstract Intent registerReceiver(
                               BroadcastReceiver receiver, 
                               IntentFilter      filter, 
                               String            broadcastPermission, 
                               Handler           scheduler)

method.

If the broadcastPermission argument is non-null then the registered BroadcastReceiver cannot receive broadcast Intents from any Application which has not been granted the specified permission.

If the scheduler argument is non-null then the registered BroadcastReceiver’s onReceive() method will be executed in the context of the specified Handler.

If it is not necessary to specify either a permission or a Handler then an implementation of the
android.content.Context

    public abstract Intent registerReceiver(
                               BroadcastReceiver receiver,  
                               IntentFilter      filter)

method can be used instead.

Both methods will return either a broadcast Intent which was sent in sticky mode which matches the given IntentFilter, or null. (See also).

Both methods can also be used to access a stick broadcast Intent directly.

The same BroadcastReceiver can be registered multiple times with different IntentFilters.

4.2 Unregistration

A dynamically registered BroadcastReceiver can be unregistered using an implementation of the android.content.Context

    public abstract void unregisterReceiver(BroadcastReceiver receiver)

The effect of this method is undo the effects of all calls to either of the registerReceiver() methods used to register the given BroadcastReceiver.

5. BroadcastReceiver Lifecycles

The lifecycle of a BroadcastReceiver differs depending upon whether it was registered statically or dynamically.

5.1 The Static BroadcastReceiver Lifecycle

The lifecycle of a statically registered BroadcastReceiver is under the control of the System.

When a broadcast Intent is to be delivered to a statically registered BroadcastReceiver the System will

  1. create the appropriate process in which to run it if necessary

  2. create an instance of the BroadcastReceiver and invoke its onReceive() method

Once the onReceive() method has returned the System may stop the process used to run it.

The transient nature of a statically registered BroadcastReceiver means that its onReceive() method cannot use any functionality which is asynchronous, for example, binding to a Service.

This constraint is enforced by the implementation of the Context passed to the method at runtime. For example, its implementation of the bindService() method throws a RuntimeException.

5.2 The Dynamic BroadcastReceiver Lifecycle

The lifecycle of a dyamically registered BroadcastReceiver is under the control of the Application.

An Application can create BroadcastReceivers and register and unregister them as and when it chooses.

There are no constraints on the functionality that can be used by the implementation of the onReceive() method of a dynamically registered BroadcastReceiver.

6. BroadcastReceivers And Intent Resolution

If an Intent explicitly specifies a component then the Intent resolves to that Component if it is a BroadcastReceiver. If the specified Component is not a BroadcastReceiver it is equivalent to the case where the Intent cannot be resolved to any BroadcastReceiver(s).

Otherwise a search is made for all BroadcastReceivers with an associated IntentFilter which matches the given Intent, as defined by the IntentFilter.match() method.

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

An Application can determine the BroadcastReceivers to which a given Intent resolves by using an implementation od the android.content.pm.PackageManager

    public abstract List queryBroadcastReceivers (Intent intent, int flags)

The list is sorted in order from high to low priority as defined by the associated IntentFilters.

7. Broadcasting

A normal broadcast Intent can be sent using an implementation of the android.content.Context

    public abstract void sendBroadcast(Intent intent, String receiverPermission)

method.

If the receiverPermission argument is non-null then a BroadcastReceiver cannot receive the broadcast Intent being sent unless it was declared by an Application which has been granted the given permission.

The method is asynchronous. It returns immediately, and the delivery of the broadcast Intent to the set of eligible BroadcastReceivers executes independently of the method’s caller.

If it is not necessary to specify a permission then an implementation of the android.content.Context

    public abstract void sendBroadcast(Intent intent)

can be used instead.

8. Ordered Broadcasting

8.1 Sending An Ordered Broadcast Intent

A simple ordered broadcast Intent can be sent using an implementation of the android.content.Context

    public abstract void sendOrderedBroadcast(
                             Intent intent, 
                             String receiverPermission)

method.

If the receiverPermission argument is non-null then a BroadcastReceiver cannot receive the broadcast Intent being sent unless it was declared by an Application which has been granted the given permission.

The method is asynchronous. It returns immediately, and the process of sending the ordered broadcast Intent executes independently of the method’s caller.

8.2 Sending An Ordered Broadcast Intent And Getting A “Result”

An implementation of the android.content.Context method

    public abstract void sendOrderedBroadcast(
                             Intent            intent, 
                             String            receiverPermission, 
                             BroadcastReceiver resultReceiver, 
                             Handler           scheduler, 
                             int               initialCode, 
                             String            initialData, 
                             Bundle            initialExtras)

can be used to send an ordered broadcast Intent with associated data and obtain a result.

If the receiverPermission argument is non-null then the broadcast Intent being sent cannot be received by any BroadcastReceiver registered by an Application which has not been granted the specified permission.

If the resultReceiver argument is non-null it specifies a BroadcastReceiver whose onReceive() method will be invoked when the sending of the ordered broadcast Intent completes.

If the scheduler argument is non-null and the resultReceiver argument is also non-null then the onReceive() method of the BroadcastReceiver will be run in the context of the specified Handler.

The

  • initialCode

  • initialData

  • initialExtras

arguments specify the initial values of the

  • code

  • data

  • extras

elements respectively of the additional data associated with the sending of the ordered broadcast Intent.

The method is asynchronous. It returns immediately, and the process of sending the ordered broadcast Intent executes independently of the method’s caller.

8.2.1 BroadcastReceivers And Ordered Broadcast Intents

The BroadcastReceiver class defines a number of methods related to ordered broadcast Intents which can be used by an implementation of the onReceive() method.

8.2.1.1 Determining The “Type” Of A Broadcast Intent

The

    public final boolean isOrderedBroadcast()

method will return true if the onReceive()method has been invoked with an ordered broadcast Intent.

8.2.1.2 Getting The Ordered Broadcast Intent “Result” Data

The methods

  • public final int getResultCode()

  • public final String getResultData()

  • public final Bundle getResultExtras(boolean makeMap)</p

will return the current values of the

  • code

  • data

  • extras

respectively.

If the code is not defined then the default value is -1.

If the data is not defined then the default value is null.

If the extras are not defined then the default value is null unless the makeMap argument is true in which case an empty Bundle is created and returned.

These methods can be called when the onReceive() method was not invoked on an ordered broadcast Intent and they will return the default values as above.

8.2.1.3 Setting The Ordered Broadcast Intent “Result” Data

The methods

  • public final void setResultCode(int code)

  • public final void setResultData(String data)

  • public final void setResultExtras (Bundle extras)

will set the current values of the

  • code

  • data

  • extras

respectively.

Alternatively all three can be set simultaneously using the

    public final void setResult(int code, String data, Bundle extras)

method.

Using any of these methods when the onReceive() method has not been invoked on an ordered broadcast Intent will result in a RuntimeException, except in one situation.

8.2.1.4 Stopping The Process Of Sending Of An Ordered Broadcast Intent

A BroadcastReceiver can stop the process of sending an ordered broadcast Intent by calling the

    public final void abortBroadcast()

method.

Any BroadcastReceivers of the same priority that have not already received the Intent and all those with a lower priority than the current BroacastReceiver will not receive the Intent.

Using this method when the onReceive() method has not been invoked on an ordered broadcast Intent will result in a RuntimeException, except in one situation.

8.3 Ordered Broadcast Intent Examples

There are four BroadcastReceivers registered statically as follows

    <receiver 
        android:name = "OrderedBroadcastReceiverOne">
        <intent-filter 
            android:priority = "1">
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_ONE"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_TWO"/>
        </intent-filter>
    </receiver>
	
    <receiver 
        android:name = "OrderedBroadcastReceiverTwoA">
        <intent-filter 
            android:priority = "2">
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_ONE"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_TWO"/>
        </intent-filter>
    </receiver>
	
    <receiver 
        android:name = "OrderedBroadcastReceiverTwoB">
        <intent-filter 
            android:priority = "2">
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_ONE"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_TWO"/>
        </intent-filter>
    </receiver>
   
    <receiver 
        android:name = "OrderedBroadcastReceiverThree">
        <intent-filter 
            android:priority = "3">
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_ONE"/>
            <action 
                android:name = "xper.example.ORDERED_BROADCAST_INTENT_TWO"/>
            </intent-filter>
    </receiver>

Their respective onReceive() methods are defined as follows

    // OrderedBroadcastReceiverOne.onReceive()

    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("One: " + intent);
        System.out.println("\tgetResultCode() => " + getResultCode());
        System.out.println("\tgetResultData() => " + getResultData());
        setResultCode(getResultCode() + 1);
        setResultData(getResultData() + ", One");
		
        Bundle extras = getResultExtras(true);
		   
        extras.putString("One.value", "One");
        setResultExtras(extras);
        System.out.println("One Done");
    }
	
    ...
	
    // OrderedBroadcastReceiverTwoA.onReceive()
	 
    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("TwoA");
        System.out.println(intent);
        System.out.println("\tgetResultCode() => " + getResultCode());
        System.out.println("\tgetResultData() => " + getResultData());
        setResultCode(getResultCode() + 20);
        setResultData(getResultData() + ", TwoA");
	   
        Bundle extras = getResultExtras(true);
	   
        extras.putString("TwoA.value", "TwoA");
        setResultExtras(extras);
        if ("xper.example.ORDERED_BROADCAST_INTENT_TWO".equals(intent.getAction())) 
        {
            abortBroadcast();
        }
        System.out.println("TwoA done");
    }
	
    ...
	
    // OrderedBroadcastReceiverTwoB.onReceive()
	
    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("TwoB");
        System.out.println(intent);
        System.out.println("\tgetResultCode() => " + getResultCode());
        System.out.println("\tgetResultData() => " + getResultData());
        setResultCode(getResultCode() + 20);
        setResultData(getResultData() + ", TwoB");
		
        Bundle extras = getResultExtras(true);
		   
        extras.putString("TwoB.value", "TwoB");
        setResultExtras(extras);
        if ("xper.example.ORDERED_BROADCAST_INTENT_TWO".equals(intent.getAction())) 
        {
            abortBroadcast();
        }
        System.out.println("TwoB done");
    }
	
    ...
	
    // OrderedBroadcastReceiverThree.onReceive()
	
    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("Three");
        System.out.println(intent);
        System.out.println("\tgetResultCode() => " + getResultCode());
        System.out.println("\tgetResultData() => " + getResultData());
        setResultCode(getResultCode() + 300);
        setResultData(getResultData() + ", Three");
		
        Bundle extras = getResultExtras(true);
		   
        extras.putString("Three.value", "Three");
        setResultExtras(extras);
        System.out.println("Three done");
   }

There is a class ResultReceiver which is a sub-class of the class BroadcastReceiver.
Its onReceive() method is defined as follows.

    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("ResultReceiver");
        System.out.println(intent);
        System.out.println("\tgetResultCode() => " + getResultCode());
        System.out.println("\tgetResultData() => " + getResultData());
	    
        Bundle extras = getResultExtras(true);
	    
        System.out.println("\tBegin Extras");
        for (String key : extras.keySet())
        {
            System.out.print("\t\t");
            System.out.print(key);
            System.out.print("\t=> ");
            System.out.println(extras.get(key));
        }
        System.out.println("\tEnd Extras");
        System.out.println("ResultReceiver done");
    }

8.3.1 Example One

Sending an ordered broadcast Intent which is handled by all the registered BroadcastReceivers.

This example illustrates the delivery of the broadcast Intent to the BroadcastReceivers in priority order, and the use of result data

8.3.1.1 Stage One: Sending The Intent

The Intent is sent as follows

    sendOrderedBroadcast(
        new Intent(
            "xper.example.ORDERED_BROADCAST_INTENT"), 
        null, 
        new ResultReceiver(), 
        null, 
        0, 
        "ExampleOne", 
        null);

8.3.1.2 Stage Two: The Broadcast Intent Is Received By OrderedBroadcastReceiverThree

The OrderedBroadcastReceiverThree.onReceive() method prints

    Three
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT cmp=xper.example.three/.OrderedBroadcastReceiverThree }
        getResultCode() => 0
        getResultData() => ExampleOne
    Three done

8.3.1.3 Stage Three: The Broadcast Intent Is Received By OrderedBroadcastReceiverTwoA

The OrderedBroadcastReceiverTwoA.onReceive() method prints

    TwoA
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT cmp=xper.example.two.a/.OrderedBroadcastReceiverTwoA }
        getResultCode() => 300
        getResultData() => ExampleOne, Three
    TwoA done

8.3.1.4 Stage Four: The Broadcast Intent Is Received By OrderedBroadcastReceiverTwoB

The OrderedBroadcastReceiverTwoB.onReceive() method prints

    TwoB
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT cmp=xper.example.two.b/.OrderedBroadcastReceiverTwoB }
        getResultCode() => 320
        getResultData() => ExampleOne, Three, TwoA
    TwoB done

8.3.1.5 Stage Five: The Broadcast Intent Is Received By OrderedBroadcastReceiverOne

The OrderedBroadcastReceiverOne.onReceive() method prints

    One: Intent { act=xper.example.ORDERED_BROADCAST_INTENT cmp=xper.example.one/.OrderedBroadcastReceiverOne }
        getResultCode() => 340
        getResultData() => ExampleOne, Three, TwoA, TwoB
    One Done

8.3.1.6 Stage Six: ResultReceiver onReceive() Method Called

The ResultReceiver.onReceive() method prints

    ResultReceiver
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT }
        getResultCode() => 341
        getResultData() => ExampleOne, Three, TwoA, TwoB, One
        Begin Extras
            Three.value	=> Three
            TwoB.value	=> TwoB
            TwoA.value	=> TwoA
            One.value	=> One
        End Extras
    ResultReceiver done

8.3.2 Example Two

Sending an ordered broadcast Intent which is handled by all the registered BroadcastRecivers but one of them stops the sending process.

8.3.2.1 Stage One: Sending The Intent

The Intent is sent as follows

    sendOrderedBroadcast(
        new Intent(
            "xper.example.ORDERED_BROADCAST_INTENT_TWO"), 
        null, 
        new ResultReceiver(), 
        null, 
        0, 
        "ExampleTwo", 
        null);

8.3.2.2 Stage Two: The Broadcast Intent Is Received By OrderedBroadcastReceiverThree

The OrderedBroadcastReceiverThree.onReceive() method prints

    Three
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT_TWO cmp=xper.example.three/.OrderedBroadcastReceiverThree }
        getResultCode() => 0
        getResultData() => ExampleTwo
    Three done

8.3.2.3 Stage Three: The Broadcast Intent Is Received By OrderedBroadcastReceiverTwoA

The OrderedBroadcastReceiverTwoA.onReceive() method prints

    TwoA
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT_TWO cmp=xper.example.two.a/.OrderedBroadcastReceiverTwoA }
        getResultCode() => 300
        getResultData() => ExampleTwo, Three
    TwoA done

and stops the sending of the broadcast Intent.

8.3.2.4 Stage Four: ResultReceiver onReceive() Method Called

The ResultReceiver.onReceive() method prints

    ResultReceiver
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT_TWO }
        getResultCode() => 320
        getResultData() => ExampleTwo, Three, TwoA
        Begin Extras
            Three.value	=> Three
             TwoA.value	=> TwoA
        End Extras
    ResultReceiver done

8.3.3 Example Three

Sending an ordered broadcast Intent which is not handled by any of the registered BroadcastReecivers.

8.3.3.1 Stage One: Sending The Intent

The Intent is sent as follows

    sendOrderedBroadcast(
        new Intent(
            "xper.example.ORDERED_BROADCAST_INTENT_FOUR"), 
	    null, 
	    new ResultReceiver(), 
	    null, 
	    -3, 
	    "ExampleThree", 
	    null);

8.3.3.2 Stage Two: ResultReceiver onReceive() Method Called

The ResultReceiver.onReceive() method prints

    ResultReceiver
    Intent { act=xper.example.ORDERED_BROADCAST_INTENT_FOUR }
        getResultCode() => -3
        getResultData() => ExampleThree
        Begin Extras
        End Extras
    ResultReceiver done

9. Sticky Broadcast Intents

Both normal and ordered broadcasts can also be performed in sticky mode.

To send a broadcast Intent in sticky mode an Application must have been granted the BROADCAST_STICKY permission.

9.1 Sending A Sticky Broadcast Intent

A normal broadcast Intent can be sent in sticky mode using an implementation of the android.content.Context

    public abstract void sendStickyBroadcast(Intent intent)

method.

This works in the same way as the short-form method for sending a normal broadcast Intent.

9.2 Sending A Sticky Ordered Broadcast Intent

An ordered broadcast Intent can be sent in sticky mode using an implementation of the android.content.Context

    public abstract void sendStickyOrderedBroadcast(
                             Intent            intent, 
                             BroadcastReceiver resultReceiver, 
                             Handler           scheduler, 
                             int               initialCode, 
                             String            initialData, 
                             Bundle            initialExtras)

method.

This works in the same way as the long-form method for sending an ordered broadcast Intent.

9.3 Sticky Broadcast Intent “Replacement”

If when a broadcast Intent is sent in sticky mode it is found to match a sticky broadcast Intent sent previously then it will replace
the existing one. The Intent.filterEquals() method is used to determine whether Intents match.

One implication of this is that there can be multiple sticky broadcast Intents with, for example, the same action but different
data URIs, since these will not match.

Conversely, broadcast Intents that differ only in their extras will match.

Note also that an ordered broadcast Intent sent in sticky mode can replace a normal broadcast Intent sent in sticky mode, and vice-versa.

For example, the following


    IntentFilter f = new IntentFilter("xper.sticky.BROADCAST_INTENT");
        
    sendStickyBroadcast(
        new Intent(
                "xper.sticky.BROADCAST_INTENT").
            putExtra(
                "Type", 
                "Normal"));
    System.out.println(registerReceiver(null, f).getStringExtra("Type"));
    sendStickyOrderedBroadcast(
        new Intent(
                "xper.sticky.BROADCAST_INTENT").
            putExtra(
                "Type", 
                "Ordered"), 
        null, 
        null, 
        0, 
        null, 
        null);
    System.out.println(registerReceiver(null, f).getStringExtra("Type"));
    sendStickyBroadcast(
        new Intent(
                "xper.sticky.BROADCAST_INTENT").
            putExtra(
                "Type", 
                "Normal")); 
    System.out.println(registerReceiver(null, f).getStringExtra("Type"));

will print


    Normal
    Ordered
    Normal

9.4 Accessing A Sticky Broadcast Intent Directly

Both registerReceiver() methods can be passed a receiver argument of null. As in the non-null receiver argument case if one or more sticky broadcast Intents match the supplied IntentFilter then one of them will be returned from the method.

For example, although it is not documented as such the broadcast Intent with the action

    android.net.conn.CONNECTIVITY_CHANGE

is sent in sticky mode and hence is accesible in this way.

On the emulator the following code

    IntentFilter f = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    Intent       i = registerReceiver(null, f);
        
    System.out.println(i);
        
    Bundle b = i.getExtras();
        
    for (String key : b.keySet())
    {
        System.out.print(key);
        System.out.print(" => ");
        System.out.println(b.get(key));
    }

prints

    Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x10000000 (has extras) }
    networkInfo => NetworkInfo: type: mobile[UMTS], state: CONNECTED/CONNECTED, reason: simLoaded, ... [elided]
    reason => simLoaded
    extraInfo => internet
    inetCondition => 0

Although it is possible to use the long-form of the registerReceiver() method in this way there is effectively no point since the permission argument has no effect.

For example, replacing the line

   Intent i = registerReceiver(null, f);

in the example above with

   Intent i = registerReceiver(null, f, "xper.permission.NO_SUCH_PERMISSION", null);

does not change the behaviour at all.

9.5 Removing A Sticky Broadcast Intent

A sticky broadcast Intent can be removed by calling an implementation of the android.content.Context method

    public abstract void removeStickyBroadcast(Intent intent)

To remove a sticky broadcast Intent an Application must have been granted the BROADCAST_STICKY permission.

The method will remove the sticky broadcast Intent, if any, which matches, as determined by the Intent.filterEquals() method, the Intent passed as the intent argument.

9.6 Sticky Broadcast Intents And Dynamically Registered BroadcastReceivers

9.6.1 Registration

When a BroadcastReceiver is registered dynamically using the short-form registerReceiver() method, then, if the associated IntentFilter matches one or more sticky broadcast Intents

  • one of the matching Intents will be returned by the method, and then, at some point

  • the BroadcastReceiver’s onReceive() method will be invoked for each matching sticky broadcast Intent

For example, if we define the class StickyBroadcastReceiver as follows

    public class StickyBroadcastReceiver 
                 extends 
                     BroadcastReceiver 
    {
        @Override
        public void onReceive(Context context, Intent intent) 
        {
            System.out.println("StickyBroadcastReceiver.onReceive(...)");
            System.out.println(intent);
            System.out.println("\tisInitialStickyBroadcast() => " + isInitialStickyBroadcast());
            System.out.println("StickyBroadcastReceiver.onReceive(...) done");
        }
    }

then the following code

    Intent i = registerReceiver(
                   new StickyBroadcastReceiver(), 
                   new IntentFilter(
                       ConnectivityManager.CONNECTIVITY_ACTION));
    	
    System.out.println("registerReceiver() => " + i);

prints

    registerReceiver() => Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x10000000 (has extras) }

and then the onReceive() method prints

    StickyBroadcastReceiver.onReceive(...)
    Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x10000000 (has extras) }
        isInitialStickyBroadcast() => true
    StickyBroadcastReceiver.onReceive(...) done

When a BroadcastReceiver is registered dynamically using the long-form registerReceiver() method then, if the associated IntentFilter matches one or more sticky broadcast Intents, the exact behaviour depends on whether or not a permission is specified.

The method will return one of the matching Intents irrespective of whether or not a permission was specified.

However, once the method has returned, if a permission was specified, then the BroadcastReceiver’s onReceive() method will not be invoked on any sticky broadcast Intent sent by an Application which has not been granted that permission.

For example, modifying the previous example, then the following code

 Intent i = registerReceiver(
                new StickyBroadcastReceiver(), 
                new IntentFilter(
                    ConnectivityManager.CONNECTIVITY_ACTION),
                "xper.permission.NO_SUCH_PERMISSION",
                null);

prints

    registerReceiver() => Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x10000000 (has extras) }

and that is it. The onReceive() method is not invoked.

The behaviour with respect to the onReceive() method is consistent with the way permissions work, it is really the behaviour of the registerReceiver() method which is anomalous.

9.6.2 The onReceive() Method

A dynamically registered BroadcastReceiver can determine whether it is being invoked on a sticky broadcast Intent by calling the

    public final boolean isInitialStickyBroadcast()

method.

If it is then the behaviour of some of the methods defined by the BroadcastReceiver class for use with ordered broadcast Intents is slightly different.

  • The isOrderedBroadcast() method always returns false

  • The methods for setting result data do not throw RuntimeExceptions, they are simply no-ops

  • The abortBroadcast() method does not throw a RuntimeException, it is simply a no-op

10. Broadcast Intents And Intent Flags

There are two Intent class constants which define flags specifically for use with broadcast Intents.

10.1 FLAG_RECEIVER_REGISTERED_ONLY

If this flag is set in a broadcast Intent then it will only be delivered to those eligible BroadcastReceivers which were dynamically registered.

10.2 FLAG_RECEIVER_REPLACE_PENDING

If this flag is set in a broadcast Intent then it will replace any broadcast Intent which matches it, as defined by Intent.filterEquals(), which is currently in the process of being delivered to any eligible BroadcastReceivers.

This effect is not atomic. Some BroadcastReceivers may receive both the original and the replacement broadcast Intent, others only the replacement, as the following rather contrived example demonstrates.

We define two static BroadcastReceivers

    <receiver 
        android:name="Sole">
        <intent-filter>
            <action 
                android:name="xper.receiver.intent.RECEIVER_SOLE_INTENT"/>
            <action 
                android:name="xper.receiver.intent.RECEIVER_SEA_AREA_INTENT"/>
        </intent-filter>
    </receiver>

    <receiver 
        android:name="Fastnet">
        <intent-filter>
            <action 
                android:name="xper.receiver.intent.RECEIVER_FASTNET_INTENT"/>
            <action 
                android:name="xper.receiver.intent.RECEIVER_SEA_AREA_INTENT"/>
        </intent-filter>
    </receiver>

each in a separate Application, and one dynamic BroadcastReceiver registered by a third Application as follows

    IntentFilter f = new IntentFilter();
        
    f.addAction("xper.receiver.intent.RECEIVER_LUNDY_INTENT");
    f.addAction("xper.receiver.intent.RECEIVER_SEA_AREA_INTENT");
    registerReceiver(new Lundy(), f);

We define their respective onReceive() methods to be

    // Sole

    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("Sole.onReceive(..., " + intent + ")");
        System.out.println("Sole.onReceive(...) N == " + intent.getStringExtra("N"));
    }
	
    ...

    // Fastnet
	
    public void onReceive(Context context, Intent intent)
    {
        System.out.println("Fastnet.onReceive(..., " + intent + ")");
        System.out.println("Fastnet.onReceive(...) N == " + intent.getStringExtra("N"));
    }
	
    ...
	
    // Lundy
	
    public void onReceive(Context context, Intent intent) 
    {
        System.out.println("Lundy.onReceive(..., " + intent + ")");
        System.out.println("Lundy.onReceive(...) N == " + intent.getStringExtra("N"));
    }

If we execute the following

    sendBroadcast(
        new Intent(
                "xper.receiver.intent.RECEIVER_SEA_AREA_INTENT").
            setFlags(
                Intent.FLAG_RECEIVER_REPLACE_PENDING).
            putExtra(
                "N", 
                "One"));
    sendBroadcast(
        new Intent(
                "xper.receiver.intent.RECEIVER_SEA_AREA_INTENT").
            setFlags(
                Intent.FLAG_RECEIVER_REPLACE_PENDING).
            putExtra(
                "N", 
                "Two"));
    sendBroadcast(
        new Intent(
                "xper.receiver.intent.RECEIVER_SEA_AREA_INTENT").
            setFlags(
                Intent.FLAG_RECEIVER_REPLACE_PENDING).
            putExtra(
                "N", 
                "Three"));

then we get (output slightly reformatted)

    Lundy.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT flg=0x20000000 (has extras) })
    Lundy.onReceive(...) N == One

    Fastnet.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT \ 
        flg=0x20000000 cmp=xper.receiver.fastnet/.Fastnet (has extras) })
    Fastnet.onReceive(...) N == One

    Lundy.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT flg=0x20000000 (has extras) })
    Lundy.onReceive(...) N == Two

    Lundy.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT flg=0x20000000 (has extras) })
    Lundy.onReceive(...) N == Three

    Sole.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT \
        flg=0x20000000 cmp=xper.receiver.sole/.Sole (has extras) })
    Sole.onReceive(...) N == One

    Fastnet.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT \  
        flg=0x20000000 cmp=xper.receiver.fastnet/.Fastnet (has extras) })
    Fastnet.onReceive(...) N == Three

    Sole.onReceive(..., Intent { act=xper.receiver.intent.RECEIVER_SEA_AREA_INTENT \
        flg=0x20000000 cmp=xper.receiver.sole/.Sole (has extras) })
    Sole.onReceive(...) N == Three

11. Sending A Broadcast Intent To A Specific BroadcastReceiver

Both normal and ordered broadcast Intents can be sent to a specific BroadcastReceiver by setting the broadcast Intent’s component explicitly.

For example, assuming the Application package is

    xper.specific

and the BroadcastReceiver is declared as follows

    <receiver 
        android:name = "SpecificBroadcastReceiver">
        <intent-filter>
            <action android:name = "xper.specific.SPECIFIC_BROADCAST_INTENT"/>
        </intent-filter>
    </receiver>

then a normal broadcast Intent can be sent to it as follows.

    sendBroadcast(
        new Intent(
                "xper.specific.BROADCAST_INTENT").
            setClassName(
                "xper.specific", 
                "xper.specific.SpecificBroadcastReceiver"));

Note that, as in this example, the Intent does not have to match the IntentFilter(s) associated with the BroadcastReceiver, which has some interesting implications.

Although specifying the BroadcastReceiver explicitly when sending the broadcast Intent overrides the BroadcastReceiver’s IntentFilter(s) both sender and/or receiver permissions, if specified, still apply.

It is not possible to send a broadcast Intent to a specific BroadcastReceiver in sticky mode. Attempting to do so results in a SecurityException.

12. Anonymous BroadcastReceivers

It is possible to statically register a BroadcastReceiver without any IntentFilters. For example.

    <receiver 
        android:name = "AnonymousBroadcastReceiver"/>

Broadcast Intents can still be sent to it by specifying the BroadcastReceiver explicitly.

In this can it can only be done from the registering Application since in the BroadcastReceiver has not been exported.

It is of course possible to export it as well

    <receiver 
        android:name     = "AnonymousBroadcastReceiver" 
        android:exported = "true"/>

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

February 16, 2011

The Android Intent Based APIs: Part Five – Services And Intents

1.0 A Service/Intent Model

  • A Service is an Application component capable of executing without a UI.

  • A Service can be used to implement functionality for use by other Application Components in the same or other Applications.

  • A Service can be started and stopped.

  • A client can bind to a Service to obtain a handle to use to access the functionality provided by that Service.

  • A client can unbind from a Service that it has previously bound to.

  • An Application can statically register one or more Services

  • A Service can be registered with one or more IntentFilters which specify the Intents which can be used to start, stop, and bind to it.

  • A Service can be registered with an associated permission that an Application must have been granted in order to access that Service.

  • The System will automatically create an instance of a Service Component and a process to run it if necessary.

2.0 Services And Intent Resolution

If an Intent explicitly specifies a component then the Intent resolves to that Component even if it is not a Service or does not exist.

Otherwise a search is made for all those Services with an associated Intent Filter which matches the Intent, as defined by the IntentFilter.match() method.

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

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

An Application can determine what Service, if any, an Intent resolves to using an implementation of the android.content.pm.PackageManager

    public abstract ResolveInfo resolveService(Intent intent, int flags)

method.

An Application can obtain a list of all Services which can handle a given Intent by using an implementation the android.content.pm.PackageManager

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

method.

If the list is not empty then it is ordered from most to least specific with the Service to which the Intent will actually resolve being the first element.

3.0 Starting A Service

A Service can be started by using an implementation of the android.content.Context

    public abstract ComponentName startService(Intent service)

method.

The Intent passed as the service argument specifies the Service to start.

If the given Intent resolves to a Service then the method returns a ComponentName which identifies it. If the identified Service is not already running then the System will run it creating a process if necessary and an instance of the Service component.

If the given Intent does not resolve to a Service the method returns null.

If the given Intent resolves to a Service but the Application calling the method has not been granted the permission necessary to access it then a SecurityException is thrown.

The method is asynchronous in the sense that if the Service is not already running the method will return before the System has actually started it.

4.0 Stopping A Service

A Service can be stopped by using an implementation of the android.content.Context

    public abstract boolean stopService(Intent service)

method.

The Intent passed as the service argument specifies the Service to stop.

The method returns true if the given Intent resolves to a running Service, false otherwise.

If the given Intent resolves to a running Service but the Application calling this method has not beeen granted the permission necessary to access it a SecurityException is thrown.

The method is asynchronous in the sense that the method can return true before the Service is stopped.

5.0 Binding To A Service

A client can bind to a Service using an implementation of the android.content.Context

    public abstract boolean bindService(Intent service, ServiceConnection conn, int flags)

method.

The Intent passed as the service argument specifies the Service to bind to.

The conn argument is used to specify an object which represents the binding to the Service and which implements the android.content.ServiceConnection interface.

This interface defines two methods

    public abstract void onServiceConnected(ComponentName name, IBinder service)

and

    public abstract void onServiceDisconnected(ComponentName name)

which are invoked asynchronously when the binding to the Service is set-up and broken-down respectively.

The flags argument is used to specify zero or more bit-flags. Probably the most useful of these is specified by the android.content.Context class-constant

    BIND_AUTO_CREATE

If this flag is set then the System will if necessary create an instance of the Service Component and, again if necessary, a process to run it in.

The method returns true if the given Intent resolves to a Service, otherwise false.

If the given Intent resolves to a Service but the Application calling the method has not been granted the permission necessary to access it then a SecurityException is thrown.

The effects of successfully binding to a Service are dependent upon the state of the Service at that point.

5.1 Binding To A Running Service

If the Service is already running then once the method has returned the binding object’s implementation of the onServiceConnected() method will be invoked with the name of the Service that has been bound to and an instance of IBinder that can be used to interact with that Service.

5.2 Binding To A Service That Is Not Running

The effects of sucessfully binding to a Service that is not running are dependent upon whether the BIND_AUTO_CREATE flag is set in the flags argument.

5.2.1 When BIND_AUTO_CREATE Is Set

If the BIND_AUTO_CREATE flag is set then the System will automatically run the Service, and once the method has returned the binding
object’s implementation of the onServiceConnected() method will be invoked with the name of the Service that has been bound to and an instance of IBinder that can be used to interact with that Service.

5.2.1 When BIND_AUTO_CREATE Is Not Set

If the BIND_AUTO_CREATE flag is not set then once the method returns nothing further will happen. The binding object’s implementation of the onServiceConnected() method will not be called unless and until the Service is run for another reason, for example, in response to an Application’s call to startService().

6.0 Associating Services And Intents

An Application can specify the Intents that can be used to interact with a given Service by defining one or more intent-filter elements as children of the service element used to define the Service itself.

For example,

    <service 
        android:name = "Malin">
        <intent-filter 
            android:priority = "4">
            <action 
                android:name = "xper.service.intent.SERVICE_MALIN_INTENT"/>
            <action 
                android:name = "xper.service.intent.SERVICE_SEA_AREA_INTENT"/>
        </intent-filter>
    </service>

7.0 Service Implementation And Intents

7.1 Starting

Each time the startService() method is called and a matching Service is found, then that Service’s

    public int onStartCommand(Intent intent, int flags, int startId)

method is invoked.

A Service implementation can override the default implementation of this method to access the Intent used to start it.

This makes it possible to pass arguments to a Service when starting it, and/or use the startService() method as a one-way IPC mechanism.

7.2 Stopping

There is no way for a Service implementation to access the Intent used to stop it.

7.3 Binding

Each time the bindService() method is called and a matching Service is found then that Service’s implementation of the android.app.Service

    public abstract IBinder onBind(Intent intent)

method is invoked at some point.

Hence the Intent used to bind to a Service is always accessible to the Service implementation.

8.0 Interacting With A Specific Service

It is possible to start, stop, or bind to a specific Service by explicitly specifying the Service Component in the Intent being used to perform the operation.

For example, if the following is defined

    <service 
        android:name = "Rockall">
        <intent-filter>
            <action 
                android:name = "xper.service.intent.SERVICE_ROCKALL_INTENT"/>
            <action 
                android:name = "xper.service.intent.SERVICE_SEA_AREA_INTENT"/>
        </intent-filter>
    </service>

in the Application package

    xper.service.rockall

then the Service can be started by executing the following

    startService(
        new Intent(
                "xper.service.intent.NON_EXISTENT_SERVICE_INTENT").
            setClassName(
                "xper.service.rockall", 
                "xper.service.rockall.Rockall").
            setType(
                "nonexistent/type"));

Note that the presence of an explicit component in an Intent overrides everything else in the Intent.

One quite interesting implication of this is that any Service which is not explicitly protected by a permission and whose Component name can be discovered can be sent an Intent containing absolutely anything.

9.0 “Anonymous” Services

It is possible to specify a Service without any associated IntentFilters, for example,

    <service android:name = "AnonymousService"/>

The Service can still be started, stopped, and bound to by using an Intent with an explicit component name as described above.

For example, assuming the Service above is defined in the Application package xper.app.anonymousservice, then it can be started like this

    startService(
        new Intent().
            setClass(
                "xper.app.anonymousservice",
                "xper.app.anonymousservice.AnonymousService"))

but only from within the Application that defines the Service. Attempting to interact with the Service from anywhere else will result in a SecurityException.


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

February 8, 2011

“Alien Dalvik” ?

Filed under: Alien Dalvik, Android, Dalvik, Java, Java VM, Mobile Java — Tags: , , , , — Simon Lewis @ 5:51 pm

It is going to be interesting to find out exactly how this works.

Despite the name it cannot simply be a standalone Dalvik port. After all, all Dalvik can do is run Dexes. In fact the following would seem to suggest that it might not involve a Dalvik VM at all

Alien Dalvik enables the majority of Android applications to run unmodified, allowing application store owners to quickly kick start Android application store services by simply repackaging Android Package (APK) files.

Its not clear why you would want to mess with the APKs unless you wanted to do something else at the same time, especially if they are signed, but who knows ?

Presumably unmodified in this context refers to the source code ?

To run the majority of Android applications unmodified it must implement all the standard Java APIs and the Android APIs used by those applications some how. If the source code does not need to be modified then all those classes and methods that are referenced need to be in the runtime.

Implementing a large part of what is approximately Java 5 plus a large part of some version of the Android APIs (which version is of course another problem) is not exactly trivial given that many of the Android API methods are actually native methods, or call native methods almost immediately, and they often use Android platform specific features, for example, Skia and Surfaceflinger to name but two. In fact it is quite difficult to see how it can run the majority of Android applications unmodified unless it actually contains what amounts to a largish chunk of, not to put to fine a point on it, Android.

Which leads on to the question of where this chunk might live ? Presumably it is not per downloaded application, so the chunk must (?) be pre-installed on the device, and it is this that the device manufacturer will be licensing (?).

There is also the question of memory footprint. Android relies quite heavily on code sharing via the Zygote mechanism to keep its memory footprint down. It is possible to achieve the same effect using the standard fork()/exec() mechanism if your VM has been written with that in mind, although this is one of the techniques that Oracle argues they have a patent on. This is not necessarily a problem if the underlying VM technology is licensed from Oracle, which it might be in this case.

Then there is the question of native code. Presumably Android applications that use native code need not apply ?

And then there is security. Android applications are sand-boxed on the basis of UIDs and permissions are enforced on the basis of GIDs. If the host OS does not have these or their equivalents then there is a problem. Equally if host OS native applications are not themselves sand-boxed, even if Android applications cannot get out of their sand-boxes, everything else on the platform may be able to get into them

And if there are host OS native applications as well then how do they co-exist without some form of integration at the window manager (or equivalent) level ?

As I say it will be interesting to find out how it actually works


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

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.