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 Intent
allowing 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 PendingIntent
and 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 Intent
which will open our application and then just wrap it in PendingIntent
before 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 PendingIntent
by 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 Intent
that 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 PendingIntent
because 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 Intent
but it is not. So how does it work?
Besides the method send()
in PendingIntent
which 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 Intent
contained in PendingIntent
but rather used to populate parameters from the wrapped Intent
that 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 thisIntent
… 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 receiveIntent
if at all possible.If you try to override the values in
PendingIntent
which was created withFLAG_IMMUTABLE
, a silent crash will occur, and the original wrappedIntent
will be transferred unchanged.
Remember, the app can always update its own PendingIntent
even 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 PendingIntent
created 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 PendingIntents
created without a flag FLAG_IMMUTABLE
were 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 Intent
which allows the system or other application to run Intent
created 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.