Did you ever notice?
- Sometimes tapping a notification 🔔 and nothing happens for a couple of minutes, then it suddenly pops up? 😬
Starting with Android 12 notifications will not work if they do not start activities directly.
What is a Notification trampoline?
Some apps respond to notification taps by launching an app component (Service, BroadcastReceiver
) that starts the activity that the user finally sees and interacts with. This app component is known as a notification trampoline.
Android12 restrictions
- Applications that target Android 12 or higher can’t start
activities
fromservices or broadcast receivers
that are used as notification trampolines.
Or
- In simple words after the user taps on a notification or an action button within the notification, the app cannot
call startActivity()
inside of aservice or broadcast receiver
which helps to improve the app performance and user experience.
Error message in logcat
The system prevents the activity from starting when an application tries to start an activity
from a service or broadcast receiver that acts as a
notification trampoline
, and the following message appears in Logcat:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \ | |
this should be avoided for performance reasons. |
Sample code that will create this issue
- Trampoline component: Here we have a
BroadcastReceiver
that willstart an activity
when the user taps on the notification action.
class NotificationReceiver : BroadcastReceiver() { | |
override fun onReceive(context: Context, intent: Intent) { | |
// from Android12 this will not work | |
// Indirect notification activity start (trampoline) from PACKAGE_NAME, \ this should be | |
//avoided for performance reasons. | |
context.startActivity(Intent(context, NotificationTrampolineActivity::class.java).apply { | |
flags = Intent.FLAG_ACTIVITY_NEW_TASK | |
}) | |
} | |
} |
val broadcastIntent = Intent(context.applicationContext, NotificationReceiver::class.java) | |
val actionIntent = PendingIntent.getBroadcast( | |
context.applicationContext, | |
0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT or | |
// mutability flag required when targeting Android12 or higher | |
PendingIntent.FLAG_IMMUTABLE | |
) | |
// notification code | |
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) | |
.setSmallIcon(R.mipmap.ic_launcher) | |
.setContentTitle("Android12") | |
.setContentText("Notification trampoline restrictions") | |
.addAction(R.mipmap.ic_launcher, "Open activity from receiver", actionIntent) | |
.setAutoCancel(true) | |
.build() | |
notificationManager.notify(getUniqueId(), notification) |
NotificationWithReceiverIntent.kt
Identify which app components act as notification trampolines
- During the testing of the application, after you tap on a notification, you can identify which service or broadcast receiver acted as the notification trampoline in your app.
- To do so, look at the output of the following terminal command:
adb shell dumpsys activity service \ com.android.systemui/.dump.SystemUIAuxiliaryDumpService | |
A section of the output includes the text "NotifInteractionLog". | |
This section contains the information that's necessary to identify the component that starts as the result of a notification tap. |
Job Offers
Toggle the behavior
During the testing of a debuggable version of the application, we can enable and disable this restriction using the NOTIFICATION_TRAMPOLINE_BLOCK
app compatibility flag.
You can find it under the developer options
App compatibility flag
Update your app
- If you find that your application starts an
activity from a service or broadcast receiver
that acts as a notification trampoline, complete the following migration steps :
- Create a
PendingIntent
object that is associated with theactivity
that users see after they tap on the notification. - Use the
PendingIntent
object which is created in the previous step as part of building your notification.
val notificationTrampolineActivityIntent = | |
Intent(context.applicationContext, NotificationTrampolineActivity::class.java) | |
// Create the TaskStackBuilder | |
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run { | |
// Add the intent, which inflates the back stack | |
addNextIntentWithParentStack(notificationTrampolineActivityIntent) | |
// Get the PendingIntent containing the entire back stack | |
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or | |
// mutability flag required when targeting Android12 or higher | |
PendingIntent.FLAG_IMMUTABLE) | |
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) | |
.setSmallIcon(R.mipmap.ic_launcher) | |
.setContentTitle("Android12") | |
.setContentText("Notification trampoline restrictions fix") | |
.addAction(R.mipmap.ic_launcher, "Open activity", resultPendingIntent) | |
.setAutoCancel(true) | |
.build() | |
notificationManager.notify(getUniqueId(), notification) |