Loading services/core/java/com/android/server/notification/NotificationManagerService.java +30 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.Ranking.R import static android.service.notification.NotificationListenerService.Ranking.RANKING_UNCHANGED; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; Loading Loading @@ -414,6 +415,12 @@ public class NotificationManagerService extends SystemService { static final long SNOOZE_UNTIL_UNSPECIFIED = -1; /** * The threshold, in milliseconds, to determine whether a notification has been * cleared too quickly. */ private static final int NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS = 5000; static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; Loading Loading @@ -4817,9 +4824,12 @@ public class NotificationManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final long identity = Binder.clearCallingIdentity(); boolean notificationsRapidlyCleared = false; final String pkg; try { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); pkg = info.component.getPackageName(); // Cancellation reason. If the token comes from assistant, label the // cancellation as coming from the assistant; default to LISTENER_CANCEL. Loading @@ -4838,11 +4848,19 @@ public class NotificationManagerService extends SystemService { !mUserProfiles.isCurrentProfile(userId)) { continue; } notificationsRapidlyCleared = notificationsRapidlyCleared || isNotificationRecent(r); cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), userId, reason); } } else { for (NotificationRecord notificationRecord : mNotificationList) { if (isNotificationRecent(notificationRecord)) { notificationsRapidlyCleared = true; break; } } if (lifetimeExtensionRefactor()) { cancelAllLocked(callingUid, callingPid, info.userid, REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(), Loading @@ -4855,11 +4873,23 @@ public class NotificationManagerService extends SystemService { } } } if (notificationsRapidlyCleared) { mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, callingUid, pkg, /* attributionTag= */ null, /* message= */ null); } } finally { Binder.restoreCallingIdentity(identity); } } private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) { if (!rapidClearNotificationsByListenerAppOpEnabled()) { return false; } return notificationRecord.getFreshnessMs(System.currentTimeMillis()) < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS; } /** * Handle request from an approved listener to re-enable itself. * Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +175 −0 Original line number Diff line number Diff line Loading @@ -974,6 +974,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return new NotificationRecord(mContext, sbn, channel); } private NotificationRecord generateNotificationRecord(NotificationChannel channel, long postTime) { final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0); return new NotificationRecord(mContext, sbn, channel); } private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) { return generateNotificationRecord(channel, 1, userId); } Loading Loading @@ -13440,6 +13446,175 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel specific notifications via listener. String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is logged. verify(mAppOpsManager).noteOpNoThrow( AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null); } @Test public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create old notifications. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr1); final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel specific notifications via listener. String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled() throws RemoteException { mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel specific notifications via listener. String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged due to flag being disabled. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel all notifications via listener. mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is logged. verify(mAppOpsManager).noteOpNoThrow( AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null); } @Test public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create old notifications. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr1); final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel all notifications via listener. mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled() throws RemoteException { mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel all notifications via listener. mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged due to flag being disabled. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0, Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +30 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.Ranking.R import static android.service.notification.NotificationListenerService.Ranking.RANKING_UNCHANGED; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; Loading Loading @@ -414,6 +415,12 @@ public class NotificationManagerService extends SystemService { static final long SNOOZE_UNTIL_UNSPECIFIED = -1; /** * The threshold, in milliseconds, to determine whether a notification has been * cleared too quickly. */ private static final int NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS = 5000; static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; Loading Loading @@ -4817,9 +4824,12 @@ public class NotificationManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final long identity = Binder.clearCallingIdentity(); boolean notificationsRapidlyCleared = false; final String pkg; try { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); pkg = info.component.getPackageName(); // Cancellation reason. If the token comes from assistant, label the // cancellation as coming from the assistant; default to LISTENER_CANCEL. Loading @@ -4838,11 +4848,19 @@ public class NotificationManagerService extends SystemService { !mUserProfiles.isCurrentProfile(userId)) { continue; } notificationsRapidlyCleared = notificationsRapidlyCleared || isNotificationRecent(r); cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), userId, reason); } } else { for (NotificationRecord notificationRecord : mNotificationList) { if (isNotificationRecent(notificationRecord)) { notificationsRapidlyCleared = true; break; } } if (lifetimeExtensionRefactor()) { cancelAllLocked(callingUid, callingPid, info.userid, REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(), Loading @@ -4855,11 +4873,23 @@ public class NotificationManagerService extends SystemService { } } } if (notificationsRapidlyCleared) { mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, callingUid, pkg, /* attributionTag= */ null, /* message= */ null); } } finally { Binder.restoreCallingIdentity(identity); } } private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) { if (!rapidClearNotificationsByListenerAppOpEnabled()) { return false; } return notificationRecord.getFreshnessMs(System.currentTimeMillis()) < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS; } /** * Handle request from an approved listener to re-enable itself. * Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +175 −0 Original line number Diff line number Diff line Loading @@ -974,6 +974,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return new NotificationRecord(mContext, sbn, channel); } private NotificationRecord generateNotificationRecord(NotificationChannel channel, long postTime) { final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0); return new NotificationRecord(mContext, sbn, channel); } private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) { return generateNotificationRecord(channel, 1, userId); } Loading Loading @@ -13440,6 +13446,175 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel specific notifications via listener. String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is logged. verify(mAppOpsManager).noteOpNoThrow( AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null); } @Test public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create old notifications. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr1); final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel specific notifications via listener. String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled() throws RemoteException { mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel specific notifications via listener. String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged due to flag being disabled. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel all notifications via listener. mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is logged. verify(mAppOpsManager).noteOpNoThrow( AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null); } @Test public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException { mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create old notifications. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr1); final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel all notifications via listener. mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } @Test public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled() throws RemoteException { mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); // Create recent notification. final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, System.currentTimeMillis()); mService.addNotification(nr1); // Create old notification. final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); mService.addNotification(nr2); // Cancel all notifications via listener. mService.getBinderService().cancelNotificationsFromListener(null, null); waitForIdle(); // Notifications should not be active anymore. StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); assertThat(notifications).isEmpty(); assertEquals(0, mService.getNotificationRecordCount()); // Ensure cancel event is not logged due to flag being disabled. verify(mAppOpsManager, never()).noteOpNoThrow( eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), any(), any()); } private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,