Loading services/core/java/com/android/server/notification/NotificationManagerService.java +33 −7 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR; import static android.app.Flags.lifetimeExtensionRefactor; import static android.app.Flags.updateRankingTime; import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO; import static android.app.Notification.EXTRA_LARGE_ICON_BIG; Loading Loading @@ -1321,7 +1320,29 @@ public class NotificationManagerService extends SystemService { // Notifications that have been interacted with should no longer be lifetime // extended. if (lifetimeExtensionRefactor()) { r.getSbn().getNotification().flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; // Enqueue a cancellation; this cancellation should only work if // the notification still has FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY // We wait for 200 milliseconds before posting the cancel, to allow the app // time to update the notification in response instead. // If that update goes through, the notification won't have the lifetime // extended flag, and this cancellation will be dropped. mHandler.scheduleCancelNotification( new CancelNotificationRunnable( callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY /*=mustHaveFlags*/, FLAG_NO_DISMISS /*=mustNotHaveFlags*/, false /*=sendDelete*/, r.getUserId(), REASON_APP_CANCEL, -1 /*=rank*/, -1 /*=count*/, null /*=listener*/, SystemClock.elapsedRealtime()), 200); } } } Loading Loading @@ -9337,7 +9358,11 @@ public class NotificationManagerService extends SystemService { } } protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) { protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable, int delay) { if (lifetimeExtensionRefactor()) { sendMessageDelayed(Message.obtain(this, cancelRunnable), delay); } else { if (Flags.notificationReduceMessagequeueUsage()) { sendMessage(Message.obtain(this, cancelRunnable)); } else { Loading @@ -9346,6 +9371,7 @@ public class NotificationManagerService extends SystemService { } } } } protected void scheduleOnPackageChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList) { Loading Loading @@ -9698,7 +9724,7 @@ public class NotificationManagerService extends SystemService { // remove notification call ends up in not removing the notification. mHandler.scheduleCancelNotification(new CancelNotificationRunnable(callingUid, callingPid, pkg, tag, id, mustHaveFlags, mustNotHaveFlags, sendDelete, userId, reason, rank, count, listener, SystemClock.elapsedRealtime())); count, listener, SystemClock.elapsedRealtime()), 0); } /** Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +109 −13 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.Notification.FLAG_USER_INITIATED_JOB; Loading Loading @@ -6048,7 +6049,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); verify(handler, timeout(300).times(0)).scheduleSendRankingUpdate(); verify(handler, times(1)).scheduleCancelNotification(any()); verify(handler, times(1)).scheduleCancelNotification(any(), eq(0)); } @Test Loading Loading @@ -6307,7 +6308,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.CancelNotificationRunnable.class); verify(handler, times(1)).scheduleCancelNotification( captor.capture()); captor.capture(), eq(0)); // Run the runnable given to the cancel notification, and see if it logs properly NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue(); Loading Loading @@ -8651,34 +8652,128 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test public void testOnNotificationActionClickLifetimeExtendedEnds() { public void testActionClickLifetimeExtendedCancel() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final Notification.Action action = new Notification.Action.Builder(null, "text", PendingIntent.getActivity( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build(); final boolean generatedByAssistant = false; // Creates a notification marked as being lifetime extended. NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; mService.addNotification(r); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); // Call on action click. NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility, /*generatedByAssistant=*/false); // The flag is removed, so the notification is no longer lifetime extended. // Lifetime extended flag persists. assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); // The record is sent out without the flag. ArgumentCaptor<NotificationRecord> captor = ArgumentCaptor.forClass(NotificationRecord.class); verify(mAssistants, times(1)).notifyAssistantActionClicked( captor.capture(), eq(action), eq(generatedByAssistant)); assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); mTestableLooper.moveTimeForward(210); waitForIdle(); verify(mWorkerHandler, times(1)) .scheduleCancelNotification( any(NotificationManagerService.CancelNotificationRunnable.class), eq(200)); // Check that the cancelation occurred and the notification is gone. notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(0); assertThat(mService.getNotificationRecordCount()).isEqualTo(0); } @Test public void testActionClickLifetimeExtendedCancel_PreventByNoDismiss() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final Notification.Action action = new Notification.Action.Builder(null, "text", PendingIntent.getActivity( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build(); // Creates a notification marked as being lifetime extended. NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; // Make the notification non-dismissable r.getSbn().getNotification().flags |= FLAG_NO_DISMISS; mService.addNotification(r); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); // Call on action click. NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility, /*generatedByAssistant=*/false); // Lifetime extended flag persists. assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); mTestableLooper.moveTimeForward(210); waitForIdle(); verify(mWorkerHandler, times(1)) .scheduleCancelNotification( any(NotificationManagerService.CancelNotificationRunnable.class), eq(200)); // The cancellation is dropped and the notification is still present, with the update. notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); } @Test public void testUpdateOnActionClickDropsLifetimeExtendedCancel() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final Notification.Action action = new Notification.Action.Builder(null, "text", PendingIntent.getActivity( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build(); // Creates a notification marked as being lifetime extended. NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; mService.addNotification(r); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); // Call on action click. NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility, /*generatedByAssistant=*/false); // The "app" sends an update of the notification in response. mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getNotification(), r.getSbn().getUserId()); mTestableLooper.moveTimeForward(210); waitForIdle(); verify(mWorkerHandler, times(1)) .scheduleCancelNotification( any(NotificationManagerService.CancelNotificationRunnable.class), eq(200)); // The cancellation is dropped and the notification is still present, with the update. notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); } @Test Loading Loading @@ -8706,6 +8801,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } @Test public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); Loading Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +33 −7 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR; import static android.app.Flags.lifetimeExtensionRefactor; import static android.app.Flags.updateRankingTime; import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO; import static android.app.Notification.EXTRA_LARGE_ICON_BIG; Loading Loading @@ -1321,7 +1320,29 @@ public class NotificationManagerService extends SystemService { // Notifications that have been interacted with should no longer be lifetime // extended. if (lifetimeExtensionRefactor()) { r.getSbn().getNotification().flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; // Enqueue a cancellation; this cancellation should only work if // the notification still has FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY // We wait for 200 milliseconds before posting the cancel, to allow the app // time to update the notification in response instead. // If that update goes through, the notification won't have the lifetime // extended flag, and this cancellation will be dropped. mHandler.scheduleCancelNotification( new CancelNotificationRunnable( callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY /*=mustHaveFlags*/, FLAG_NO_DISMISS /*=mustNotHaveFlags*/, false /*=sendDelete*/, r.getUserId(), REASON_APP_CANCEL, -1 /*=rank*/, -1 /*=count*/, null /*=listener*/, SystemClock.elapsedRealtime()), 200); } } } Loading Loading @@ -9337,7 +9358,11 @@ public class NotificationManagerService extends SystemService { } } protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) { protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable, int delay) { if (lifetimeExtensionRefactor()) { sendMessageDelayed(Message.obtain(this, cancelRunnable), delay); } else { if (Flags.notificationReduceMessagequeueUsage()) { sendMessage(Message.obtain(this, cancelRunnable)); } else { Loading @@ -9346,6 +9371,7 @@ public class NotificationManagerService extends SystemService { } } } } protected void scheduleOnPackageChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList) { Loading Loading @@ -9698,7 +9724,7 @@ public class NotificationManagerService extends SystemService { // remove notification call ends up in not removing the notification. mHandler.scheduleCancelNotification(new CancelNotificationRunnable(callingUid, callingPid, pkg, tag, id, mustHaveFlags, mustNotHaveFlags, sendDelete, userId, reason, rank, count, listener, SystemClock.elapsedRealtime())); count, listener, SystemClock.elapsedRealtime()), 0); } /** Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +109 −13 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.Notification.FLAG_USER_INITIATED_JOB; Loading Loading @@ -6048,7 +6049,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); verify(handler, timeout(300).times(0)).scheduleSendRankingUpdate(); verify(handler, times(1)).scheduleCancelNotification(any()); verify(handler, times(1)).scheduleCancelNotification(any(), eq(0)); } @Test Loading Loading @@ -6307,7 +6308,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.CancelNotificationRunnable.class); verify(handler, times(1)).scheduleCancelNotification( captor.capture()); captor.capture(), eq(0)); // Run the runnable given to the cancel notification, and see if it logs properly NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue(); Loading Loading @@ -8651,34 +8652,128 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test public void testOnNotificationActionClickLifetimeExtendedEnds() { public void testActionClickLifetimeExtendedCancel() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final Notification.Action action = new Notification.Action.Builder(null, "text", PendingIntent.getActivity( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build(); final boolean generatedByAssistant = false; // Creates a notification marked as being lifetime extended. NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; mService.addNotification(r); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); // Call on action click. NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility, /*generatedByAssistant=*/false); // The flag is removed, so the notification is no longer lifetime extended. // Lifetime extended flag persists. assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); // The record is sent out without the flag. ArgumentCaptor<NotificationRecord> captor = ArgumentCaptor.forClass(NotificationRecord.class); verify(mAssistants, times(1)).notifyAssistantActionClicked( captor.capture(), eq(action), eq(generatedByAssistant)); assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); mTestableLooper.moveTimeForward(210); waitForIdle(); verify(mWorkerHandler, times(1)) .scheduleCancelNotification( any(NotificationManagerService.CancelNotificationRunnable.class), eq(200)); // Check that the cancelation occurred and the notification is gone. notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(0); assertThat(mService.getNotificationRecordCount()).isEqualTo(0); } @Test public void testActionClickLifetimeExtendedCancel_PreventByNoDismiss() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final Notification.Action action = new Notification.Action.Builder(null, "text", PendingIntent.getActivity( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build(); // Creates a notification marked as being lifetime extended. NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; // Make the notification non-dismissable r.getSbn().getNotification().flags |= FLAG_NO_DISMISS; mService.addNotification(r); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); // Call on action click. NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility, /*generatedByAssistant=*/false); // Lifetime extended flag persists. assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0); mTestableLooper.moveTimeForward(210); waitForIdle(); verify(mWorkerHandler, times(1)) .scheduleCancelNotification( any(NotificationManagerService.CancelNotificationRunnable.class), eq(200)); // The cancellation is dropped and the notification is still present, with the update. notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); } @Test public void testUpdateOnActionClickDropsLifetimeExtendedCancel() throws Exception { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); final Notification.Action action = new Notification.Action.Builder(null, "text", PendingIntent.getActivity( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build(); // Creates a notification marked as being lifetime extended. NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; mService.addNotification(r); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); // Call on action click. NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility, /*generatedByAssistant=*/false); // The "app" sends an update of the notification in response. mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getNotification(), r.getSbn().getUserId()); mTestableLooper.moveTimeForward(210); waitForIdle(); verify(mWorkerHandler, times(1)) .scheduleCancelNotification( any(NotificationManagerService.CancelNotificationRunnable.class), eq(200)); // The cancellation is dropped and the notification is still present, with the update. notifs = mBinderService.getActiveNotifications(mPkg); assertThat(notifs.length).isEqualTo(1); assertThat(mService.getNotificationRecordCount()).isEqualTo(1); } @Test Loading Loading @@ -8706,6 +8801,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } @Test public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); Loading