Just An Application

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.

Create a free website or blog at WordPress.com.

%d bloggers like this: