Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -38864,6 +38864,7 @@ package android.service.notification { field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 field public static final int REASON_ASSISTANT_CANCEL = 22; // 0x16 field public static final int REASON_CANCEL = 2; // 0x2 field public static final int REASON_CANCEL_ALL = 3; // 0x3 field public static final int REASON_CHANNEL_BANNED = 17; // 0x11 core/java/android/service/notification/NotificationListenerService.java +6 −1 Original line number Diff line number Diff line Loading @@ -256,6 +256,8 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_CHANNEL_REMOVED = 20; /** Notification was canceled due to the app's storage being cleared */ public static final int REASON_CLEAR_DATA = 21; /** Notification was canceled due to an assistant adjustment update. */ public static final int REASON_ASSISTANT_CANCEL = 22; /** * @hide Loading @@ -279,7 +281,10 @@ public abstract class NotificationListenerService extends Service { REASON_UNAUTOBUNDLED, REASON_CHANNEL_BANNED, REASON_SNOOZED, REASON_TIMEOUT REASON_TIMEOUT, REASON_CHANNEL_REMOVED, REASON_CLEAR_DATA, REASON_ASSISTANT_CANCEL, }) public @interface NotificationCancelReason{}; Loading services/core/java/com/android/server/notification/NotificationManagerService.java +31 −4 Original line number Diff line number Diff line Loading @@ -83,6 +83,7 @@ import static android.service.notification.NotificationListenerService.NOTIFICAT import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; Loading Loading @@ -476,6 +477,14 @@ public class NotificationManagerService extends SystemService { @LoggingOnly private static final long RATE_LIMIT_TOASTS = 174840628L; /** * Whether listeners understand the more specific reason provided for notification * cancellations from an assistant, rather than using the more general REASON_LISTENER_CANCEL. */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L; private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; Loading Loading @@ -4388,6 +4397,13 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); // Cancellation reason. If the token comes from assistant, label the // cancellation as coming from the assistant; default to LISTENER_CANCEL. int reason = REASON_LISTENER_CANCEL; if (mAssistants.isServiceTokenValidLocked(token)) { reason = REASON_ASSISTANT_CANCEL; } if (keys != null) { final int N = keys.length; for (int i = 0; i < N; i++) { Loading @@ -4400,7 +4416,7 @@ public class NotificationManagerService extends SystemService { } cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), userId); r.getSbn().getId(), userId, reason); } } else { cancelAllLocked(callingUid, callingPid, info.userid, Loading Loading @@ -4494,12 +4510,13 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { int callingUid, int callingPid, String pkg, String tag, int id, int userId, int reason) { int mustNotHaveFlags = FLAG_ONGOING_EVENT; cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */, mustNotHaveFlags, true, userId, REASON_LISTENER_CANCEL, info); userId, reason, info); } /** Loading Loading @@ -4641,13 +4658,17 @@ public class NotificationManagerService extends SystemService { try { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); int cancelReason = REASON_LISTENER_CANCEL; if (mAssistants.isServiceTokenValidLocked(token)) { cancelReason = REASON_ASSISTANT_CANCEL; } if (info.supportsProfiles()) { Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) " + "from " + info.component + " use cancelNotification(key) instead."); } else { cancelNotificationFromListenerLocked(info, callingUid, callingPid, pkg, tag, id, info.userid); pkg, tag, id, info.userid, cancelReason); } } } finally { Loading Loading @@ -11049,6 +11070,12 @@ public class NotificationManagerService extends SystemService { && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) { reason = REASON_CHANNEL_BANNED; } // apps before T don't know about REASON_ASSISTANT, so replace it with the // previously-used case, REASON_LISTENER_CANCEL if (!CompatChanges.isChangeEnabled(NOTIFICATION_LOG_ASSISTANT_CANCEL, info.uid) && reason == REASON_ASSISTANT_CANCEL) { reason = REASON_LISTENER_CANCEL; } listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason); } catch (RemoteException ex) { Slog.e(TAG, "unable to notify listener (removed): " + info, ex); Loading services/core/java/com/android/server/notification/NotificationRecordLogger.java +7 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.notification; import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; Loading Loading @@ -180,7 +181,9 @@ public interface NotificationRecordLogger { + " shade.") NOTIFICATION_CANCEL_USER_SHADE(192), @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen") NOTIFICATION_CANCEL_USER_LOCKSCREEN(193); NOTIFICATION_CANCEL_USER_LOCKSCREEN(193), @UiEvent(doc = "Notification was canceled due to an assistant adjustment update.") NOTIFICATION_CANCEL_ASSISTANT(906); private final int mId; NotificationCancelledEvent(int id) { Loading @@ -206,6 +209,9 @@ public interface NotificationRecordLogger { if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) { return NotificationCancelledEvent.values()[reason]; } if (reason == REASON_ASSISTANT_CANCEL) { return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT; } if (NotificationManagerService.DBG) { throw new IllegalArgumentException("Unexpected cancel reason " + reason); } Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +53 −0 Original line number Diff line number Diff line Loading @@ -4658,6 +4658,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(2, mNotificationRecordLogger.get(1).getInstanceId()); } @Test public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception { NotificationManagerService.WorkerHandler handler = mock( NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); // Set up notifications: r1 is adjusted, r2 is not final NotificationRecord r1 = generateNotificationRecord( mTestNotificationChannel, 1, null, true); r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); mService.addNotification(r1); final NotificationRecord r2 = generateNotificationRecord( mTestNotificationChannel, 2, null, true); r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); mService.addNotification(r2); // Test an adjustment that sets importance to none (meaning it's cancelling) Bundle signals1 = new Bundle(); signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_NONE); Adjustment adjustment1 = new Adjustment( r1.getSbn().getPackageName(), r1.getKey(), signals1, "", r1.getUser().getIdentifier()); mBinderService.applyAdjustmentFromAssistant(null, adjustment1); // Actually apply the adjustments & recalculate importance when run doAnswer(invocationOnMock -> { ((NotificationRecord) invocationOnMock.getArguments()[0]) .applyAdjustments(); ((NotificationRecord) invocationOnMock.getArguments()[0]) .calculateImportance(); return null; }).when(mRankingHelper).extractSignals(any(NotificationRecord.class)); // run the CancelNotificationRunnable when it happens ArgumentCaptor<NotificationManagerService.CancelNotificationRunnable> captor = ArgumentCaptor.forClass( NotificationManagerService.CancelNotificationRunnable.class); verify(handler, times(1)).scheduleCancelNotification( captor.capture()); // Run the runnable given to the cancel notification, and see if it logs properly NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue(); runnable.run(); assertEquals(1, mNotificationRecordLogger.numCalls()); assertEquals( NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT, mNotificationRecordLogger.event(0)); } @Test public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); Loading Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -38864,6 +38864,7 @@ package android.service.notification { field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 field public static final int REASON_ASSISTANT_CANCEL = 22; // 0x16 field public static final int REASON_CANCEL = 2; // 0x2 field public static final int REASON_CANCEL_ALL = 3; // 0x3 field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
core/java/android/service/notification/NotificationListenerService.java +6 −1 Original line number Diff line number Diff line Loading @@ -256,6 +256,8 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_CHANNEL_REMOVED = 20; /** Notification was canceled due to the app's storage being cleared */ public static final int REASON_CLEAR_DATA = 21; /** Notification was canceled due to an assistant adjustment update. */ public static final int REASON_ASSISTANT_CANCEL = 22; /** * @hide Loading @@ -279,7 +281,10 @@ public abstract class NotificationListenerService extends Service { REASON_UNAUTOBUNDLED, REASON_CHANNEL_BANNED, REASON_SNOOZED, REASON_TIMEOUT REASON_TIMEOUT, REASON_CHANNEL_REMOVED, REASON_CLEAR_DATA, REASON_ASSISTANT_CANCEL, }) public @interface NotificationCancelReason{}; Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +31 −4 Original line number Diff line number Diff line Loading @@ -83,6 +83,7 @@ import static android.service.notification.NotificationListenerService.NOTIFICAT import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; Loading Loading @@ -476,6 +477,14 @@ public class NotificationManagerService extends SystemService { @LoggingOnly private static final long RATE_LIMIT_TOASTS = 174840628L; /** * Whether listeners understand the more specific reason provided for notification * cancellations from an assistant, rather than using the more general REASON_LISTENER_CANCEL. */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L; private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; Loading Loading @@ -4388,6 +4397,13 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); // Cancellation reason. If the token comes from assistant, label the // cancellation as coming from the assistant; default to LISTENER_CANCEL. int reason = REASON_LISTENER_CANCEL; if (mAssistants.isServiceTokenValidLocked(token)) { reason = REASON_ASSISTANT_CANCEL; } if (keys != null) { final int N = keys.length; for (int i = 0; i < N; i++) { Loading @@ -4400,7 +4416,7 @@ public class NotificationManagerService extends SystemService { } cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), userId); r.getSbn().getId(), userId, reason); } } else { cancelAllLocked(callingUid, callingPid, info.userid, Loading Loading @@ -4494,12 +4510,13 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { int callingUid, int callingPid, String pkg, String tag, int id, int userId, int reason) { int mustNotHaveFlags = FLAG_ONGOING_EVENT; cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */, mustNotHaveFlags, true, userId, REASON_LISTENER_CANCEL, info); userId, reason, info); } /** Loading Loading @@ -4641,13 +4658,17 @@ public class NotificationManagerService extends SystemService { try { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); int cancelReason = REASON_LISTENER_CANCEL; if (mAssistants.isServiceTokenValidLocked(token)) { cancelReason = REASON_ASSISTANT_CANCEL; } if (info.supportsProfiles()) { Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) " + "from " + info.component + " use cancelNotification(key) instead."); } else { cancelNotificationFromListenerLocked(info, callingUid, callingPid, pkg, tag, id, info.userid); pkg, tag, id, info.userid, cancelReason); } } } finally { Loading Loading @@ -11049,6 +11070,12 @@ public class NotificationManagerService extends SystemService { && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) { reason = REASON_CHANNEL_BANNED; } // apps before T don't know about REASON_ASSISTANT, so replace it with the // previously-used case, REASON_LISTENER_CANCEL if (!CompatChanges.isChangeEnabled(NOTIFICATION_LOG_ASSISTANT_CANCEL, info.uid) && reason == REASON_ASSISTANT_CANCEL) { reason = REASON_LISTENER_CANCEL; } listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason); } catch (RemoteException ex) { Slog.e(TAG, "unable to notify listener (removed): " + info, ex); Loading
services/core/java/com/android/server/notification/NotificationRecordLogger.java +7 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.notification; import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; Loading Loading @@ -180,7 +181,9 @@ public interface NotificationRecordLogger { + " shade.") NOTIFICATION_CANCEL_USER_SHADE(192), @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen") NOTIFICATION_CANCEL_USER_LOCKSCREEN(193); NOTIFICATION_CANCEL_USER_LOCKSCREEN(193), @UiEvent(doc = "Notification was canceled due to an assistant adjustment update.") NOTIFICATION_CANCEL_ASSISTANT(906); private final int mId; NotificationCancelledEvent(int id) { Loading @@ -206,6 +209,9 @@ public interface NotificationRecordLogger { if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) { return NotificationCancelledEvent.values()[reason]; } if (reason == REASON_ASSISTANT_CANCEL) { return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT; } if (NotificationManagerService.DBG) { throw new IllegalArgumentException("Unexpected cancel reason " + reason); } Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +53 −0 Original line number Diff line number Diff line Loading @@ -4658,6 +4658,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(2, mNotificationRecordLogger.get(1).getInstanceId()); } @Test public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception { NotificationManagerService.WorkerHandler handler = mock( NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); // Set up notifications: r1 is adjusted, r2 is not final NotificationRecord r1 = generateNotificationRecord( mTestNotificationChannel, 1, null, true); r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); mService.addNotification(r1); final NotificationRecord r2 = generateNotificationRecord( mTestNotificationChannel, 2, null, true); r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); mService.addNotification(r2); // Test an adjustment that sets importance to none (meaning it's cancelling) Bundle signals1 = new Bundle(); signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_NONE); Adjustment adjustment1 = new Adjustment( r1.getSbn().getPackageName(), r1.getKey(), signals1, "", r1.getUser().getIdentifier()); mBinderService.applyAdjustmentFromAssistant(null, adjustment1); // Actually apply the adjustments & recalculate importance when run doAnswer(invocationOnMock -> { ((NotificationRecord) invocationOnMock.getArguments()[0]) .applyAdjustments(); ((NotificationRecord) invocationOnMock.getArguments()[0]) .calculateImportance(); return null; }).when(mRankingHelper).extractSignals(any(NotificationRecord.class)); // run the CancelNotificationRunnable when it happens ArgumentCaptor<NotificationManagerService.CancelNotificationRunnable> captor = ArgumentCaptor.forClass( NotificationManagerService.CancelNotificationRunnable.class); verify(handler, times(1)).scheduleCancelNotification( captor.capture()); // Run the runnable given to the cancel notification, and see if it logs properly NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue(); runnable.run(); assertEquals(1, mNotificationRecordLogger.numCalls()); assertEquals( NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT, mNotificationRecordLogger.event(0)); } @Test public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); Loading