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.

Advertisements

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 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.

December 16, 2010

The Android Intent Based APIs: Part Three – Intent Filters

Filed under: Android, IntentFilters, Intents, Java, Mobile Java — Tags: , , , , — Simon Lewis @ 9:37 pm

An Intent Filter specifies a set of values to be matched against Intents.

Values may be specified to match against any or all of the following parts of an Intent

  • the action

  • the data

  • the data type

  • the categories

1. Actions

An Intent Filter may specify zero or more actions to match against the action specified by an Intent.

2. Data

An Intent Filter may specify

  • a non-empty set of schemes, or

  • a non-empty set of schemes and a non-empty set of authorities, or

  • a non-empty set of schemes and a non-empty set of authorities and a non-empty set of paths

to match against the data specified by an Intent.

Although it is also possible for an Intent Filter to specify the following,

  • a non-empty set of authorities only, or

  • a non-empty set of paths only, or

  • a non-empty set of authorities and a non-empty set of paths only

they will NOT be used when matching against the data specified by an Intent.

2.2 Schemes

A scheme specifies a case-sensitive URI scheme to match against the scheme of a data URI specified by an Intent

2.3 Authorities

An authority specifies a host, and optionally a port, to match the authority part of a network data URI specified by an Intent.

2.4 Paths

A path to match against the data URI specified by an Intent can be specified as

  • a literal, which the path part of the data URI must be equal to,

  • a prefix, which the path part of the data URI must start with, or

  • a simple pattern, which the path part of the data URI must match

The syntax of a simple pattern is as follows

  • The expression <char>'*' matches zero or more occurences of <char>

  • The character '.' matches any character.

  • The character '\' escapes the following character.

3. Data Types

An IntentFilter can specify zero or more MIME types to match against the type of the data specified by an Intent.

4. Categories

An Intent Filter can specify zero or more categories to match against the set of categories specified by an Intent.

5. The intent-filter XML Element

An Intent Filter can be specified in the manifest of an Application using the

    intent-filter

element.

5.1 Attributes

All the attributes of this element pertain to the Application Component which declares it rather than to the Intent Filter itself.

5.1.1 The icon Attribute

If present the icon attribute specifies a drawable resource which the system should use to visually identify the Application Component with which this Intent Filter declaration is associated.

5.1.2 The label Attribute

If present the label attribute specifies a string resource which the system should use to label the Application Component with which this Intent Filter declaration is associated.

5.1.3 The priority Attribute

If present the priority attribute specifies an integer value to be used by the system if it is necessary to compute an ordering on a set of Application Components all of which have associated Intent Filters which match a given Intent.

5.2 Child Elements

5.2.1 The action Element

The action element has a single attribute

    name

which specifies the action. This attribute must be present.

There are no child elements.

The element must appear at least once. It may appear multiple times.

5.2.2 The category Element

The category element has a single attribute

    name

which specifies the category. This attribute must be present.

There are no child elements.

The element may appear zero or more times.

5.2.3 The data Element

The data element may appear zero or more times.

It has no child elements. All values are specified using attributes.

The exact set of attributes associated with an individual data element is not significant.

For example, these three elements

    <data scheme="http"/>

    <data host="justanapplication.wordpress.com"/>
	
    <data pathPrefix="/2010"/>

are equivalent to this one.

    <data scheme="http" host="justanapplication.wordpress.com" pathPrefix="/2010"/>

The values specified by each element are aggregated to make up the IntentFilter’s sets of schemes, authorities, paths and types.

The validity of the authority and path sets that result are governed by the rules defined here.

5.2.3.1 The Attributes

5.2.3.1.1 Specifying A Scheme

A scheme can be specified using the scheme attribute.

5.2.3.1.2 Specifying An Authority

The host and port parts of an authority can be specified using the host and port attributes respectively.

A data element may have a host attribute without an associated port attribute. However, if a data element does not have a host attribute, but it does have a port attribute, then the port attribute is ignored.

5.2.3.1.3 Specifying A Path

A path can be specified as a

using the

  • path
  • pathPrefix, or
  • pathPattern

attributes respectively.

All three attributes may be present on the same element.

5.2.3.1.3 Specifying A Data Type

A data type can be specified by using mimeType attribute.

The value of the attribute must be a valid MIME type of the form <type>/<sub-type>. It is an error if it is not.

6. The IntentFilter class

An Application can specify an Intent Filter at runtime using an instance of the class

    android.content.IntentFilter

The system itself uses sub-classes of this class to represent both statically and dynamically specified Intent Filters associated with Application Components.

An IntentFilter instance may contain

  • a set of actions

  • a set of schemes

  • a set of authorities

  • a set of paths

  • a set of MIME types

  • a set of categories

6.1 Construction

The IntentFilter class has four public constructors

    public IntentFilter()

    public IntentFilter(String action)
	
    public IntentFilter(String action, String dataType)
	
    public IntentFilter(IntentFilter o)

An instance of IntentFilter is mutable so only the default constructor is strictly necessary, but IntentFilters with a single action are fairly heavily used by the system itself hence at least one of the convenience constructors.

6.2 Actions

An action can be added to an IntentFilter’s set of actions using the method

    public final void addAction(String action)

The format of the action argument is not checked, although it cannot be null.

6.3 Data

6.3.1 Schemes

A scheme can be added to an IntentFilter’s set of schemes using the method

    public final void addDataScheme(String scheme)

The format of the scheme argument is not checked, although it cannot be null.

6.3.2 Authorities

An authority can be added to an IntentFilter’s set of authorities using the method

    public final void addDataAuthority(String host, String port)

The host argument may start with the wildcard character ('*') in which case it specifies a suffix to match. It cannot be null.

The port may be null. If it is not then it must be a representation of an integer capable of being parsed by the Integer method

    public int parseInt(String string)

or a NumberFormatException will be thrown.

Whether adding an authority using this method will actually have any effect on the matching of an Intent’s data URI depends on the rules outlined here.

6.3.3 Paths

A path can be added to the IntentFilter’s set of paths using the method

    public final void addDataPath(String path, int type)

The value of the type argument must be one of the following android.os.PatternMatcher class constants

  • PATTERN_LITERAL

  • PATTERN_PREFIX

  • PATTERN_SIMPLE_GLOB

and it specifies that the path argument is to be interpreted as a

respectively.

Whether adding a path using this method will actually have any effect effect on the matching of an Intent’s data URI depends on the rules outlined here.

6.4 Data Types

A MIME type can be added to the IntentFilter’s set of data types using the method

    public final void addDataType(String type)

Unlike the methods used to set explicitly the data type of an Intent, the format of the type argument is verified.

It must be of the form

    <type> '/' <sub-type>

Both the type and the sub-type must at least of length one.

Both the type and the sub-type can be the wildcard character '*', in which case it specifies the wildcard type which matches any type.

Alternatively only the sub-type may be the wildcard character in which it case it matches any sub-type of the given type.

6.5 Categories

A category can be added to the IntentFilter’s set of categories using the method

    public final void addCategory(String category)

The format of the category argument is not checked, although it cannot be null.

7. Matching

An IntentFilter can be used to match the following constituents of an Intent, either individually or collectively.

  • the action

  • the data

  • the data type

  • the categories

7.1 Method Return Values

Some IntentFilter matching methods return an integer status, a negative value indicating that the match failed, a positive value that the match succeeded.

7.1.1 Matching Failures

A matching failure is specified using the value of one of the IntentFilter class constants

Class Constant Value Reason
NO_MATCH_TYPE -1 type did not match
NO_MATCH_DATA -2 data did not match
NO_MATCH_ACTION -3 action did not match
NO_MATCH_CATEGORY -4 categories did not match

7.1.2 Matching Success

The integer value returned when a match succeeded can comprise two parts.

The top twelve bits always specifies the match category, that is, what matched, and the bottom sixteen bits may specify a quality adjustment

The possible values of the match category part are defined by the following class constants

Class Constant Value
MATCH_CATEGORY_EMPTY 0x100000
MATCH_CATEGORY_SCHEME 0x200000
MATCH_CATEGORY_HOST 0x300000
MATCH_CATEGORY_PORT 0x400000
MATCH_CATEGORY_PATH 0x500000
MATCH_CATEGORY_TYPE 0x600000

Although the constant returned is dependent on what matched in a given context, the distinction being made may not be that useful.

For example, the documentation for MATCH_CATEGORY_HOST reads

The filter matched an intent with the same data URI scheme and authority host.

which is true when it is returned from matchData() but not matchDataAuthority().

The documentation for MATCH_CATEGORY_TYPE reads

The filter matched an intent with the same data MIME type.

which is true as far as it goes.

MATCH_CATEGORY_TYPE is only ever returned by the method matchData() and it certainly will not return it if the IntentFilter does not match the given type, but it can mean anything from there was only a type given and it matched up to everything matched, that is, scheme, host, port, path and type all matched.

The IntentFilter class constants

    MATCH_ADJUSTMENT_MASK

and

    MATCH_CATEGORY_MASK

specify the bit masks to use to extract the respective parts.

7.2 Matching Against An Action

An IntentFilter can be used to match against an action using the IntentFilter method

    public final boolean matchAction(String action)

This method will return true if and only if the action is not null and it is a member of the IntentFilter’s set of actions.

Note

The method documentation is actually contradictory. It states that

If the filter does not specify any actions, the match will always fail.

which is correct.

It then goes on to say that it returns

True if the action is listed in the filter or the filter does not specify any actions. [Emphasis added]

the second part of which is not.

7.3 Matching Against A Data Type, Scheme, and URI

An IntentFilter can be used to match against a data type, scheme and URI using the IntentFilter method

    public final int matchData(String type, String scheme, Uri data)

The method first attempts to match against the scheme and the URI and then, against the type.

7.3.1 Matching The Scheme And URI

7.3.2.1 Case: Non-Empty Set Of Schemes

If the IntentFilter specifies one or more schemes then the match will succeed

  • if the scheme argument is non-null and it is a member of the IntentFilter’s set of schemes, or

  • it is null and the empty String (“”) is a member of the IntentFilter’s set of schemes.

7.3.2.1.1 Case: Non-Empty Set Of Schemes And Non-Empty Set Of Authorities

If the IntentFilter specifies one or more schemes and the scheme matching succeeds, then if it also specifies one or more authorities, the match will succeed if one of the specified authorities matches the authority part of the URI. The matching is done using the matchDataAuthority() method.

7.3.2.1.1.1 Case: Non-Empty Set Of Schemes And Non-Empty Set Of Authorities And Non-Empty Set Of Paths

If the IntentFilter specifies one or more schemes and the scheme matching succeeds, and it specifies one or more authorities and the authority matching succeeds, then if it also specifies one or more paths the match will succeed if the data URI has a path part which at least one of the specified paths matches.

7.3.2.2 Case: Empty Set Of Schemes And Non-Empty Set Of Types

If the IntentFilter does not specify any schemes then the match will succeed if the value of the scheme argument is one of the following

  • null

  • “” (the empty String)

  • “content”

  • “file”

7.3.2 Matching The Type

If the matching of the scheme and URI succeeds then the type is matched.

7.3.2.1 Case: Non-Empty Set Of Types

If the IntentFilter specifies one or more types then the match will succeed if one of the specified types matches the type argument.

A specified type will match the type argument

  • if it is equal to it

  • if it is the wildcard type (*/*)

  • if it is a type/wildcard pair (<type>/*) which matches.

For example, given

    IntentFilter f1 = new IntentFilter();
        
    f1.addDataType("image/png");
	
    IntentFilter f2 = new IntentFilter();
            
    f2.addDataType("*/*");
	
    IntentFilter f3 = new IntentFilter();
            
    f3.addDataType("image/*");

the following matches all succeed

    f1.matchData("image/png", null, null) // returns 0x608000

    f2.matchData("image/png", null, null) // returns 0x608000

    f3.matchData("image/png", null, null) // returns 0x608000

Rather less obviously the match will also succeed if the type argument is

  • the wildcard type (*/*), or

  • a type/wildcard pair (<type>/*) which matches a type/sub-type pair specified by the IntentFilter.

For example given

    IntentFilter f = new IntentFilter();
        	
    f.addDataType("image/png");

then the following both succeed

    f.matchData("*/*", null, null)     // returns 0x608000

    f.matchData("image/*", null, null) // returns 0x608000

The format of the type argument is not checked but if it is not in the type/sub-type format the match will fail.

For example, given the same IntentFilter as above

    IntentFilter f = new IntentFilter();
        	
    f.addDataType("image/png");

then this fails

    f.matchData("image", null, null) // returns -1

despite the fact that there is an argument for saying that image and image/* are pretty much the same thing,

It is worth noting this because when the data type of an Intent is set explicitly the format is not checked, so it quite feasible to set it to something like image.

7.3.2.2 Case: Empty Set Of Types And Non-Empty Set Of Schemes

If the IntentFilter does not specify any types then the match will only succeed if the type argument is null.

7.3.3 Edge Case (Strange But True): Empty Set Of Schemes And Empty Set Of Types

The method actually looks for this edge case first.

If the IntentFilter does not specify any schemes or types and the data and type arguments are both null then the match will succeed for any value of the scheme argument.

For example

    new IntentFilter().matchData(null, "http", null) // returns  0x108000

and also

    new IntentFilter().matchData(null, "not really a scheme at all", null) // returns 0x108000

If the data and type arguments are not both null the match will fail.

7.4 Matching Against A Data Authority

An IntentFilter can be used to match against the authority part of a URI using the IntentFilter method

    public final int matchDataAuthority(Uri data)

The match will succeed if the IntentFilter specifies one or more authorities, the URI specifies a host, or a host and a port, and at least one of the specfied authorities matches the host, or host and port, specified by the URI.

An authority will match the URI if

  • the URI specifies a host and a port, the authority specifies a host and a port, and they match, or

  • the URI specifies a host, it may or may not specify a port, the authority only specifies a host, and the specified hosts match

Note

Although the documentation is adamant that

[the] host name in the Android framework is case-sensitive, unlike formal RFC host names

nobody seems to have told the source code of this method which is using the String method

    public int compareToIgnoreCase(String string)

to do the matching of the hosts, so given

    IntentFilter f = new IntentFilter();
        
    f.addDataAuthority("justanapplication.wordpress.com", null);

and

    Uri uriLC  = new Uri.Builder().scheme("http").authority("justanapplication.wordpress.com").build();
    Uri uriMC  = new Uri.Builder().scheme("http").authority("JustAnApplication.WordPress.Com").build();
    Uri uriUC  = new Uri.Builder().scheme("http").authority("JUSTANAPPLICATION.WORDPRESS.COM").build();

then the following all succeed

    f.matchDataAuthority(uriLC) // returns 0x300000
	
    f.matchDataAuthority(uriMC) // returns 0x300000
	
    f.matchDataAuthority(uriUC) // returns 0x300000

7.5 Matching Against A Set Of Categories

An IntentFilter can be used to match against a set of categories using the IntentFilter method

    public final String matchCategories(Set<String> categories)

The match will succeed if the set specified by the categories argument is equal to, or a sub-set of, the set of categories specified by the IntentFilter.

If the match fails the method will return the first category in the categories argument which is not a member of the set of categories specified by the IntentFilter.

If the match succeeds then the method will return null.

For example, assuming there are four categories defined by the class constants

  • CATEGORY_FOO

  • CATEGORY_BAR

  • CATEGORY_BAZ

  • CATEGORY_MUMBLE

five Intents constructed as follows

    Intent i0 = new Intent();
    Intent i1 = new Intent().
                    addCategory(
                        CATEGORY_FOO);
    Intent i2 = new Intent(i1).
                    addCategory(
                        CATEGORY_BAR);
    Intent i3 = new Intent(i2).
                    addCategory(
                        CATEGORY_BAZ);
    Intent i4 = new Intent(i3).
                    addCategory(
                        CATEGORY_MUMBLE);

and an IntentFilter constructed as follows

    IntentFilter f = new IntentFilter();
        
    f.addCategory(CATEGORY_FOO);
    f.addCategory(CATEGORY_BAR);
    f.addCategory(CATEGORY_BAZ);

then the following

    f.matchCategories(i0.getCategories())    // null
	
    f.matchCategories(new HashSet<String>()) // empty set

    f.matchCategories(i1.getCategories())    // sub-set

    f.matchCategories(i2.getCategories())    // sub-set

    f.matchCategories(i3.getCategories())    // equals

will all match and return null, and this

    f.matchCategories(i4.getCategories())

will not match and will return the value of CATEGORY_MUMBLE.

Note especially that if the set of categories to match is null or empty then the match will always succeed.

7.6 Matching Against An Intent

An IntentFilter can be used to match against all the constituent parts of an Intent using the IntentFilter method


    public final int match(
                         String      action, 
                         String      type, 
                         String      scheme,
                         Uri         data, 
                         Set<String> categories, 
                         String      logTag) 

It is this method that the system uses to match IntentFilters against the parts of an Intent.

The method uses the matchAction(), matchData() and matchCategories() methods in combination to determine whether there is a match, but with one subtle difference.

While this will fail

    new IntentFilter().matchAction(null)   // returns false

this will not

    new IntentFilter().match(null, null, null, null, null, null)   // returns 0x108000

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

Blog at WordPress.com.

%d bloggers like this: