All About PendingIntents

PendingIntent are an important part of the Android framework, but most of the resources available for developers focus on the details of their implementation – the “system-backed token reference” – rather than their use.

Since Android 12 contains important innovations with respect to deferred intentions, including determining whether PendingIntent mutable or immutable, I thought it might be helpful to talk about what deferred intents do, how the system uses them, and why you might sometimes need a mutable PendingIntent

What is PendingIntent?

An object PendingIntent wraps the functionality of an object Intentallowing your application to specify what another application should do on your behalf in response to a future action. For example, a wrapped intent can be triggered when an alarm goes off or when a user taps on a notification.

The key aspect of deferred intents is that another application calls the intent on behalf of your application… That is, when an intent is called, another application uses your application’s id.

To PendingIntent had the same behavior as normal Intent, the system calls PendingIntent with the same ID it was created with. In most situations, such as alarms and notifications, this is the identifier of the application itself.

Let’s take a look at the different ways in which our applications can work with PendingIntentand why they should be used that way.

Common case

The most common and easiest way to use PendingIntent is the action associated with the notification:

val intent = Intent(applicationContext, MainActivity::class.java).apply {
    action = NOTIFICATION_ACTION
    data = deepLink
}
val pendingIntent = PendingIntent.getActivity(
    applicationContext,
    NOTIFICATION_REQUEST_CODE,
    intent,
    PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(
        applicationContext,
        NOTIFICATION_CHANNEL
    ).apply {
        // ...
        setContentIntent(pendingIntent)
        // ...
    }.build()
notificationManager.notify(
    NOTIFICATION_TAG,
    NOTIFICATION_ID,
    notification
)

We see that we are creating a standard type Intentwhich will open our application and then just wrap it in PendingIntentbefore adding it to our notification.

In this case, since we have the exact action that needs to be performed, we create PendingIntent, not subject to change by the application, where we transfer it using the flag FLAG_IMMUTABLE

After calling NotificationManagerCompat.notify () all is ready. The system will display a notification and when the user clicks on it, it will call PendingIntent.send () for our PendingIntentby launching our application.

Updating the immutable PendingIntent

You might think that if the app needs to update PendingIntent, then it should be mutable, but this is not always the case! Application creating PendingIntent, can always update it by passing the flag FLAG_UPDATE_CURRENT:

val updatedIntent = Intent(applicationContext, MainActivity::class.java).apply {
   action = NOTIFICATION_ACTION
   data = differentDeepLink
}
// Because we're passing `FLAG_UPDATE_CURRENT`, this updates
// the existing PendingIntent with the changes we made above.
val updatedPendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   updatedIntent,
   PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// The PendingIntent has been updated.

A little later we’ll talk about why you might want to do PendingIntent changeable.

Inter-app APIs technology

The common case is useful for more than just interacting with the system. Although for receive a callback after performing an action, they are most often used startActivityForResult () and onActivityResult (), this is not the only way.

Imagine an online ordering application that provides an API to integrate applications with it. It can accept PendingIntent as extra to your own Intentthat is used to start the food ordering process. Ordering app launches PendingIntent only after the order has been delivered.

In this case, the ordering application uses PendingIntent, and does not send the result of the action, because the delivery of the order can take a long time, and there is no point in making the user wait while this happens.

We will create an immutable PendingIntentbecause we don’t want the online ordering application to change anything in our Intent… It needs to be sent as it is when the order is delivered.

Modifiable PendingIntents

What if we are developers of an ordering application and want to add a function to allow the user to type a message to be sent back to the calling application? It is possible for the calling application to show something like “Now is PIZZA time!”.

The answer to this question is to use a mutable PendingIntent

Insofar as PendingIntent Is, in fact, a wrapper around Intent, you might think that there is a method PendingIntent.getIntent()which can be called to get and update the wrapped Intentbut it is not. So how does it work?

Besides the method send() in PendingIntentwhich takes no parameters, there are several other versions including thiswhich takes Intent:

fun PendingIntent.send(
    context: Context!, 
    code: Int, 
    intent: Intent?
)

This parameter intent does not replace Intentcontained in PendingIntentbut rather used to populate parameters from the wrapped Intentthat were not provided at creation PendingIntent

Let’s take an example.

val orderDeliveredIntent = Intent(applicationContext, OrderDeliveredActivity::class.java).apply {
   action = ACTION_ORDER_DELIVERED
}
val mutablePendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   orderDeliveredIntent,
   PendingIntent.FLAG_MUTABLE
)

This PendingIntent can be transferred to our online ordering application. Upon completion of delivery, the ordering application may receive a message customerMessage and send it back as an additional intent like below:

val intentWithExtrasToFill = Intent().apply {
   putExtra(EXTRA_CUSTOMER_MESSAGE, customerMessage)
}
mutablePendingIntent.send(
   applicationContext,
   PENDING_INTENT_CODE,
   intentWithExtrasToFill
)

Then the calling application will see an additional EXTRA_CUSTOMER_MESSAGE in his Intent and will be able to display the message.

Important Considerations When Declaring Pending Intents

  • When creating a mutable PendingIntent ALWAYS explicitly set the component to run in this Intent… This can be implemented as we did above, by explicitly specifying the exact class that will receive it, or by calling Intent.setComponent ()

  • There may be a case in your application where it is easier to call Intent.setPackage()… Be very careful about the possibility of matching multiple components if you do this. Better to specify a specific component to receive Intentif at all possible.

  • If you try to override the values ​​in PendingIntentwhich was created with FLAG_IMMUTABLE, a silent crash will occur, and the original wrapped Intent will be transferred unchanged.

Remember, the app can always update its own PendingIntenteven if they are immutable. The only reason to do PendingIntent mutable – if another application will be able to somehow update the wrapped Intent

Details about flags

We talked a little about several flags that can be used when creating PendingIntent, but there are others that deserve attention.

FLAG_IMMUTABLE: Indicates that Intent inside PendingIntent cannot be changed by other applications that transmit Intent in PendingIntent.send()… The app can always use FLAG_UPDATE_CURRENT to change their own PendingIntent

Before Android 12 PendingIntentcreated without this flag was mutable by default.

Android versions prior to Android 6 (API 23) PendingIntents always changeable.

FLAG_MUTABLE: Indicates that Intent inside PendingIntent should allow the app to update its content by concatenating the values ​​from the intent parameter PendingIntent.send()

Always fill in ComponentName wrapped Intent any mutable PendingIntent… Failure to do so can lead to security vulnerabilities!

This flag was added in Android 12… Before Android 12 any PendingIntentscreated without a flag FLAG_IMMUTABLEwere implicitly mutable.

FLAG_UPDATE_CURRENT: Requests the system to update an existing PendingIntent new additional data, rather than creating a new PendingIntent… If a PendingIntent was not registered, then this one is registered.

FLAG_ONE_SHOT: Allows you to send PendingIntent only once (after PendingIntent.send()). This can be important when transferring PendingIntent another application, if it contains Intent can only be sent once. This requirement is due to convenience or the need to prevent the application from performing an action multiple times.

Using FLAG_ONE_SHOT prevents problems such as “replay attacks“.

FLAG_CANCEL_CURRENT: Cancels the current PendingIntent, if it already exists, before registering a new one. This can be important if a certain PendingIntent was sent to one app and you want to send it to another app, potentially updating data. Using FLAG_CANCEL_CURRENT, the first application will no longer be able to trigger the dispatch, but the second application will.

Getting PendingIntents

Sometimes the system or other frameworks provide a PendingIntent as a response to an API call. One example is the method MediaStore.createWriteRequest ()which was added in Android 11.

static fun MediaStore.createWriteRequest(
    resolver: ContentResolver, 
    uris: MutableCollection<Uri>
): PendingIntent

Summary

We talked about the fact that PendingIntent can be thought of as a wrapper around Intentwhich allows the system or other application to run Intentcreated by one application, as this application, at a certain time in the future.

We also talked about the fact that PendingIntents should usually be immutable and that this does not prevent the application from updating its own objects PendingIntent… This can be done using the flag FLAG_UPDATE_CURRENT in addition to FLAG_IMMUTABLE

We also talked about the precautions to take – complete ComponentName wrapped Intent – if a PendingIntent must be mutable.

Finally, we talked about the fact that sometimes a system or frameworks can provide PendingIntent our application so that we can decide how and when to launch them.

Updates in PendingIntent were just one of the features in Android 12 aimed at improving application security. About all for the changes in the preview, read here

Want even more? We encourage you to test your applications for new OS preview for developers and share with us your impressions!


The translation of the material was prepared as part of the course “Android Developer. Basic”… If you are interested in learning more about the course, come to Open Day online, where the teacher will tell you about the format and training program.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *