PendingIntent Mutability in Android 12+
When targeting Android 12 (API level 31) or higher, you'll encounter the "Missing PendingIntent mutability flag" lint warning or a runtime crash. This change enhances security by requiring explicit declaration of whether your PendingIntent
can be modified by other apps.
Why This Change Matters
Starting with Android 12, PendingIntents must explicitly declare their mutability using either:
FLAG_IMMUTABLE
: The PendingIntent cannot be modified by other apps (recommended)FLAG_MUTABLE
: The PendingIntent can be modified by other apps (use only when necessary)
Solution: Add Mutability Flags
For most use cases, you should use FLAG_IMMUTABLE
:
Kotlin
val pendingIntent = PendingIntent.getActivity(
context,
REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
Java
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
);
Handling Different Android Versions
To maintain backward compatibility with older Android versions:
Kotlin
fun getPendingIntentFlags(isMutable: Boolean = false): Int {
return when {
isMutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
!isMutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ->
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
else -> PendingIntent.FLAG_UPDATE_CURRENT
}
}
Java
int getPendingIntentFlags(boolean isMutable) {
if (isMutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
} else if (!isMutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
} else {
return PendingIntent.FLAG_UPDATE_CURRENT;
}
}
When to Use Mutable vs Immutable
TIP
Use FLAG_IMMUTABLE in most cases where the PendingIntent doesn't need to be modified by other apps.
WARNING
Use FLAG_MUTABLE only when functionality requires modifying the underlying intent, such as:
- Notification inline replies
- Notification bubbles
- Other cases where the intent content needs to be updated by the system
Common Implementation Patterns
Utility Class Approach
Create a utility class to handle PendingIntent creation consistently:
object PendingIntentCompat {
@JvmStatic
fun getActivity(
context: Context,
requestCode: Int,
intent: Intent,
flags: Int,
isMutable: Boolean = false
): PendingIntent {
val finalFlags = if (isMutable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags or PendingIntent.FLAG_MUTABLE
} else {
flags
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags or PendingIntent.FLAG_IMMUTABLE
} else {
flags
}
}
return PendingIntent.getActivity(context, requestCode, intent, finalFlags)
}
// Similar methods for getService(), getBroadcast(), etc.
}
Third-Party Library Considerations
Many crashes related to PendingIntent mutability come from outdated dependencies. Update these common libraries:
WorkManager
dependencies {
def work_version = "2.8.1"
implementation "androidx.work:work-runtime:$work_version"
// For Kotlin:
implementation "androidx.work:work-runtime-ktx:$work_version"
}
Firebase
implementation 'com.google.firebase:firebase-messaging:23.4.0'
Navigation Components
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
Force Library Updates
If third-party libraries use outdated WorkManager versions, force an update:
allprojects {
configurations.all {
resolutionStrategy {
force 'androidx.work:work-runtime:2.8.1'
}
}
}
Firebase Cloud Messaging Specifics
If you're experiencing issues specifically with FCM:
WARNING
Update from deprecated FirebaseInstanceId to FirebaseMessaging:
Before (deprecated):
FirebaseInstanceId.getInstance().instanceId
.addOnSuccessListener { result ->
val token = result.token
}
After:
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
val token = task.result
}
Manifest Requirements for Android 12+
Ensure all components in your AndroidManifest.xml declare the android:exported
attribute:
<activity
android:name=".MainActivity"
android:exported="true" />
<service
android:name=".MyService"
android:exported="false" />
<receiver
android:name=".MyReceiver"
android:exported="true" />
Testing Your Implementation
After implementing these changes, test your app on:
- Android 11 and lower devices to ensure backward compatibility
- Android 12+ devices to verify the PendingIntent mutability requirement is satisfied
- Both foreground and background scenarios for notification handling
Conclusion
The PendingIntent mutability requirement in Android 12+ enhances security while maintaining functionality. By:
- Adding appropriate mutability flags (
FLAG_IMMUTABLE
orFLAG_MUTABLE
) - Updating third-party dependencies
- Ensuring proper manifest configuration
- Testing across Android versions
You can resolve the lint warning and ensure your app works correctly on all supported Android versions.