android-how to solve `Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent` problem when running android app on android 12 ?

1. Problem

When our app change the target to android sdk 31 or android 12, the app crash and we get this error message:

06/03 17:37:21: Launching 'app' on Pixel 5 API 31 android12.
Install successfully finished in 109 ms.
$ adb shell am start -n "com.bswen.wzreceiver/com.bswen.wzreceiver.ui.main.SplashActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Connected to process 11604 on device 'Pixel_5_API_31_android12 [emulator-5554]'.
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
V/GraphicsEnvironment: ANGLE Developer option for 'com.bswen.wzreceiver' set to: 'default'
V/GraphicsEnvironment: Neither updatable production driver nor prerelease driver is supported.
D/NetworkSecurityConfig: Using Network Security Config from resource network_security_config debugBuild: true
D/NetworkSecurityConfig: Using Network Security Config from resource network_security_config debugBuild: true
I/MultiDex: VM with version 2.1.0 has multidex support
I/MultiDex: Installing application
I/MultiDex: VM has multidex support, MultiDex support library is disabled.
D/WM-WrkMgrInitializer: Initializing WorkManager with default configuration.
W/FA-Ads: Disabling data collection. Found google_app_id in strings.xml but Google Analytics for Firebase is missing. Remove this value or add Google Analytics for Firebase to resume data collection.
I/DynamiteModule: Considering local module com.google.android.gms.ads.dynamite:0 and remote module com.google.android.gms.ads.dynamite:213806100
I/DynamiteModule: Selected remote version of com.google.android.gms.ads.dynamite, version >= 213806100
V/DynamiteModule: Dynamite loader version >= 2, using loadModule2NoCrashUtils
D/CompatibilityChangeReporter: Compat change id reported: 160794467; UID 10146; state: ENABLED
E/AndroidRuntime: FATAL EXCEPTION: Thread-3
    Process: com.bswen.wzreceiver, PID: 11604
    java.lang.IllegalArgumentException: com.bswen.wzreceiver: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
    Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
        at android.app.PendingIntent.checkFlags(PendingIntent.java:375)
        at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:645)
        at android.app.PendingIntent.getBroadcast(PendingIntent.java:632)
        at com.bswen.wzreceiver.utils.AlarmUtil.scheduleAlarm(AlarmUtil.java:30)
        at com.bswen.wzreceiver.App$1.run(App.java:115)
        at java.lang.Thread.run(Thread.java:920)
I/Process: Sending signal. PID: 11604 SIG: 9

The key error message is:

E/AndroidRuntime: FATAL EXCEPTION: Thread-3
    Process: com.bswen.wzreceiver, PID: 11604
    java.lang.IllegalArgumentException: com.bswen.wzreceiver: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
    Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
        at android.app.PendingIntent.checkFlags(PendingIntent.java:375)
        at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:645)
        at android.app.PendingIntent.getBroadcast(PendingIntent.java:632)
        at com.bswen.wzreceiver.utils.AlarmUtil.scheduleAlarm(AlarmUtil.java:30)
        at com.bswen.wzreceiver.App$1.run(App.java:115)
        at java.lang.Thread.run(Thread.java:920)
I/Process: Sending signal. PID: 11604 SIG: 9



2. Reason

Since Android 12 brought important updates to PendingIntents, including the need to explicitly determine if a PendingIntent is mutable, I thought it was worth talking in-depth about what a PendingIntent does, how the system uses it, and why you might need mutable types PendingIntent.

What is pending intent?

The PendingIntent object encapsulates the functionality of the Intent object, and specifies on your app’s behalf what operations other apps are allowed to perform in response to the user’s future actions. For example, the wrapped Intent might be fired after the alarm goes off or when the user clicks on the notification. The point of PendingIntent is that other apps fire the intent in your app’s name. In other words, other apps use your app’s identity to trigger intents.

In order for a PendingIntent to have the same functionality as a normal Intent, the system will use the identity when the PendingIntent was created to trigger it. In most cases, such as alarms and notifications, the identity used is the app itself.

Let’s look at the different ways PendingIntents are used in applications, and why we use them.

Basic usage of pending intent:

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
)

You can see that we built a standard type of Intent to open our app, and then simply wrapped it with a PendingIntent before adding it to the notification.

In this example, we construct a PendingIntent that cannot be modified with the FLAG_IMMUTABLE flag because we know exactly what we need to do in the future.

The job is done after calling NotificationManagerCompat.notify()). When the system displays a notification, and the user clicks on the notification, PendingIntent.send() is called on our PendingIntent to start our application.

How to update the immutable pending intent?

You might think that if the application needs to update the PendingIntent, it needs to be a mutable type, but it’s not. The PendingIntent created by the application can be updated with the FLAG_UPDATE_CURRENT flag.

val updatedIntent = Intent(applicationContext, MainActivity::class.java).apply {
   action = NOTIFICATION_ACTION
   data = differentDeepLink
}

val updatedPendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   updatedIntent,
   PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// This PendingIntent has been updated



3. Solution

the final solution is as follows:

change from this :

Intent intent = new Intent(context, SMSRetryAlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

to this:

PendingIntent pi=null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    pi = PendingIntent.getBroadcast(context, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}else {
    pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}



4. Summary

In this post, I demonstrated how to solve the Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent problem when your android app change target sdk to 31 and above, the key point is adding a PendingIntent.FLAG_IMMUTABLE flag to your PendingIntent. That’s it, thanks for your reading.

reason: