Loading services/core/java/com/android/server/notification/NotificationManagerService.java +45 −15 Original line number Diff line number Diff line Loading @@ -593,6 +593,8 @@ public class NotificationManagerService extends SystemService { static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis(); static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis(); private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; Loading Loading @@ -2637,28 +2639,49 @@ public class NotificationManagerService extends SystemService { * Cleanup broadcast receivers change listeners. */ public void onDestroy() { if (mIntentReceiver != null) { getContext().unregisterReceiver(mIntentReceiver); } if (mPackageIntentReceiver != null) { getContext().unregisterReceiver(mPackageIntentReceiver); } if (Flags.allNotifsNeedTtl()) { if (mTtlHelper != null) { mTtlHelper.destroy(); } } else { if (mNotificationTimeoutReceiver != null) { getContext().unregisterReceiver(mNotificationTimeoutReceiver); } } if (mRestoreReceiver != null) { getContext().unregisterReceiver(mRestoreReceiver); } if (mLocaleChangeReceiver != null) { getContext().unregisterReceiver(mLocaleChangeReceiver); } if (mSettingsObserver != null) { mSettingsObserver.destroy(); } if (mRoleObserver != null) { mRoleObserver.destroy(); } if (mShortcutHelper != null) { mShortcutHelper.destroy(); } if (mStatsManager != null) { mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); mStatsManager.clearPullAtomCallback(DND_MODE_RULE); } if (mAppOps != null) { mAppOps.stopWatchingMode(mAppOpsListener); } if (mAlarmManager != null) { mAlarmManager.cancelAll(); } } protected String[] getStringArrayResource(int key) { return getContext().getResources().getStringArray(key); Loading Loading @@ -8016,6 +8039,13 @@ public class NotificationManagerService extends SystemService { return false; } if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0 && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) { Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey()); mUsageStats.registerTooOldBlocked(r); return false; } return true; } Loading services/core/java/com/android/server/notification/NotificationUsageStats.java +14 −0 Original line number Diff line number Diff line Loading @@ -257,6 +257,14 @@ public class NotificationUsageStats { } } public synchronized void registerTooOldBlocked(NotificationRecord notification) { AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); for (AggregatedStats stats : aggregatedStatsArray) { stats.numTooOld++; } releaseAggregatedStatsLocked(aggregatedStatsArray); } @GuardedBy("this") private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { return getAggregatedStatsLocked(record.getSbn().getPackageName()); Loading Loading @@ -405,6 +413,7 @@ public class NotificationUsageStats { public int numUndecoratedRemoteViews; public long mLastAccessTime; public int numImagesRemoved; public int numTooOld; public AggregatedStats(Context context, String key) { this.key = key; Loading Loading @@ -535,6 +544,7 @@ public class NotificationUsageStats { maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations)); maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations)); maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved)); maybeCount("not_too_old", (numTooOld - previous.numTooOld)); noisyImportance.maybeCount(previous.noisyImportance); quietImportance.maybeCount(previous.quietImportance); finalImportance.maybeCount(previous.finalImportance); Loading Loading @@ -570,6 +580,7 @@ public class NotificationUsageStats { previous.numAlertViolations = numAlertViolations; previous.numQuotaViolations = numQuotaViolations; previous.numImagesRemoved = numImagesRemoved; previous.numTooOld = numTooOld; noisyImportance.update(previous.noisyImportance); quietImportance.update(previous.quietImportance); finalImportance.update(previous.finalImportance); Loading Loading @@ -679,6 +690,8 @@ public class NotificationUsageStats { output.append("numQuotaViolations=").append(numQuotaViolations).append("\n"); output.append(indentPlusTwo); output.append("numImagesRemoved=").append(numImagesRemoved).append("\n"); output.append(indentPlusTwo); output.append("numTooOld=").append(numTooOld).append("\n"); output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n"); output.append(indentPlusTwo).append(quietImportance.toString()).append("\n"); output.append(indentPlusTwo).append(finalImportance.toString()).append("\n"); Loading Loading @@ -725,6 +738,7 @@ public class NotificationUsageStats { maybePut(dump, "notificationEnqueueRate", getEnqueueRate()); maybePut(dump, "numAlertViolations", numAlertViolations); maybePut(dump, "numImagesRemoved", numImagesRemoved); maybePut(dump, "numTooOld", numTooOld); noisyImportance.maybePut(dump, previous.noisyImportance); quietImportance.maybePut(dump, previous.quietImportance); finalImportance.maybePut(dump, previous.finalImportance); Loading services/core/java/com/android/server/notification/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -135,3 +135,10 @@ flag { description: "This flag controls which signal is used to handle a user switch system event" bug: "337077643" } flag { name: "reject_old_notifications" namespace: "systemui" description: "This flag does not allow notifications older than 2 weeks old to be posted" bug: "339833083" } No newline at end of file services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +162 −106 Original line number Diff line number Diff line Loading @@ -112,6 +112,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS; import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL; Loading Loading @@ -339,6 +340,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; Loading Loading @@ -909,7 +911,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } mService.clearNotifications(); TestableLooper.get(this).processAllMessages(); if (mTestableLooper != null) { mTestableLooper.processAllMessages(); } try { mService.onDestroy(); Loading @@ -920,14 +924,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); if (mWorkerHandler != null) { // Remove scheduled messages that would be processed when the test is already done, and // could cause issues, for example, messages that remove/cancel shown toasts (this causes // problematic interactions with mocks when they're no longer working as expected). mWorkerHandler.removeCallbacksAndMessages(null); } if (TestableLooper.get(this) != null) { if (mTestableLooper != null) { // Must remove static reference to this test object to prevent leak (b/261039202) TestableLooper.remove(this); mTestableLooper.remove(this); } } Loading Loading @@ -1009,8 +1015,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } public void waitForIdle() { if (mTestableLooper != null) { mTestableLooper.processAllMessages(); } } private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled, int pkgPref, boolean channelEnabled) { Loading Loading @@ -1302,6 +1310,106 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return nrSummary; } private NotificationRecord createAndPostCallStyleNotification(String packageName, UserHandle userHandle, String testName) throws Exception { Person person = new Person.Builder().setName("caller").build(); Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setFlag(FLAG_USER_INITIATED_JOB, true) .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, testName, mUid, 0, nb.build(), userHandle, null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); return mService.findNotificationLocked( packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); } private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); return mService.findNotificationLocked( mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); } private static <T extends Parcelable> T parcelAndUnparcel(T source, Parcelable.Creator<T> creator) { Parcel parcel = Parcel.obtain(); source.writeToParcel(parcel, 0); parcel.setDataPosition(0); return creator.createFromParcel(parcel); } private PendingIntent createPendingIntent(String action) { return PendingIntent.getActivity(mContext, 0, new Intent(action).setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE); } private void allowTestPackageToToast() throws Exception { assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); mService.isSystemUid = false; mService.isSystemAppId = false; setToastRateIsWithinQuota(true); setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); // package is not suspended when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) .thenReturn(false); } private boolean enqueueToast(String testPackage, ITransientNotification callback) throws RemoteException { return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback); } private boolean enqueueToast(INotificationManager service, String testPackage, IBinder token, ITransientNotification callback) throws RemoteException { return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, int displayId) throws RemoteException { return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text, TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null); } private void mockIsVisibleBackgroundUsersSupported(boolean supported) { when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); } private void mockIsUserVisible(int displayId, boolean visible) { when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); } private void mockDisplayAssignedToUser(int displayId) { when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); } private void verifyToastShownForTestPackage(String text, int displayId) { verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), eq(TOAST_DURATION), any(), eq(displayId)); } @Test @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL) public void testLimitTimeOutBroadcast() { Loading Loading @@ -14069,11 +14177,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); original.when = 111; original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); assertThat(mService.mNotificationList.get(0).getNotification().when) .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) Loading @@ -14081,7 +14190,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); update.when = 222; update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); Loading @@ -14090,18 +14199,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when); } @Test public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); original.when = 111; original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); assertThat(mService.mNotificationList.get(0).getNotification().when) .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) Loading @@ -14109,7 +14219,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); update.when = 222; update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); Loading @@ -14118,7 +14228,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats, never()).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old assertThat(mService.mNotificationList.get(0).getNotification().when) .isEqualTo(original.when); // old } @Test Loading Loading @@ -15483,103 +15594,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.getTimeoutAfter()).isEqualTo(20); } private NotificationRecord createAndPostCallStyleNotification(String packageName, UserHandle userHandle, String testName) throws Exception { Person person = new Person.Builder().setName("caller").build(); Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setFlag(FLAG_USER_INITIATED_JOB, true) .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, testName, mUid, 0, nb.build(), userHandle, null, 0); @Test @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) public void testRejectOldNotification_oldWhen() throws Exception { Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis()) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); return mService.findNotificationLocked( packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); } private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); return mService.findNotificationLocked( mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); } private static <T extends Parcelable> T parcelAndUnparcel(T source, Parcelable.Creator<T> creator) { Parcel parcel = Parcel.obtain(); source.writeToParcel(parcel, 0); parcel.setDataPosition(0); return creator.createFromParcel(parcel); } private PendingIntent createPendingIntent(String action) { return PendingIntent.getActivity(mContext, 0, new Intent(action).setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE); } private void allowTestPackageToToast() throws Exception { assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); mService.isSystemUid = false; mService.isSystemAppId = false; setToastRateIsWithinQuota(true); setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); // package is not suspended when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) .thenReturn(false); } private boolean enqueueToast(String testPackage, ITransientNotification callback) throws RemoteException { return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback); } private boolean enqueueToast(INotificationManager service, String testPackage, IBinder token, ITransientNotification callback) throws RemoteException { return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, int displayId) throws RemoteException { return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text, TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null); assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isFalse(); } private void mockIsVisibleBackgroundUsersSupported(boolean supported) { when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); } @Test @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) public void testRejectOldNotification_mediumOldWhen() throws Exception { Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis()) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); private void mockIsUserVisible(int displayId, boolean visible) { when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isTrue(); } private void mockDisplayAssignedToUser(int displayId) { when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); } @Test @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) public void testRejectOldNotification_zeroWhen() throws Exception { Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setWhen(0) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); private void verifyToastShownForTestPackage(String text, int displayId) { verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), eq(TOAST_DURATION), any(), eq(displayId)); assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isTrue(); } } Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +45 −15 Original line number Diff line number Diff line Loading @@ -593,6 +593,8 @@ public class NotificationManagerService extends SystemService { static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis(); static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis(); private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; Loading Loading @@ -2637,28 +2639,49 @@ public class NotificationManagerService extends SystemService { * Cleanup broadcast receivers change listeners. */ public void onDestroy() { if (mIntentReceiver != null) { getContext().unregisterReceiver(mIntentReceiver); } if (mPackageIntentReceiver != null) { getContext().unregisterReceiver(mPackageIntentReceiver); } if (Flags.allNotifsNeedTtl()) { if (mTtlHelper != null) { mTtlHelper.destroy(); } } else { if (mNotificationTimeoutReceiver != null) { getContext().unregisterReceiver(mNotificationTimeoutReceiver); } } if (mRestoreReceiver != null) { getContext().unregisterReceiver(mRestoreReceiver); } if (mLocaleChangeReceiver != null) { getContext().unregisterReceiver(mLocaleChangeReceiver); } if (mSettingsObserver != null) { mSettingsObserver.destroy(); } if (mRoleObserver != null) { mRoleObserver.destroy(); } if (mShortcutHelper != null) { mShortcutHelper.destroy(); } if (mStatsManager != null) { mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); mStatsManager.clearPullAtomCallback(DND_MODE_RULE); } if (mAppOps != null) { mAppOps.stopWatchingMode(mAppOpsListener); } if (mAlarmManager != null) { mAlarmManager.cancelAll(); } } protected String[] getStringArrayResource(int key) { return getContext().getResources().getStringArray(key); Loading Loading @@ -8016,6 +8039,13 @@ public class NotificationManagerService extends SystemService { return false; } if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0 && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) { Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey()); mUsageStats.registerTooOldBlocked(r); return false; } return true; } Loading
services/core/java/com/android/server/notification/NotificationUsageStats.java +14 −0 Original line number Diff line number Diff line Loading @@ -257,6 +257,14 @@ public class NotificationUsageStats { } } public synchronized void registerTooOldBlocked(NotificationRecord notification) { AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); for (AggregatedStats stats : aggregatedStatsArray) { stats.numTooOld++; } releaseAggregatedStatsLocked(aggregatedStatsArray); } @GuardedBy("this") private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { return getAggregatedStatsLocked(record.getSbn().getPackageName()); Loading Loading @@ -405,6 +413,7 @@ public class NotificationUsageStats { public int numUndecoratedRemoteViews; public long mLastAccessTime; public int numImagesRemoved; public int numTooOld; public AggregatedStats(Context context, String key) { this.key = key; Loading Loading @@ -535,6 +544,7 @@ public class NotificationUsageStats { maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations)); maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations)); maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved)); maybeCount("not_too_old", (numTooOld - previous.numTooOld)); noisyImportance.maybeCount(previous.noisyImportance); quietImportance.maybeCount(previous.quietImportance); finalImportance.maybeCount(previous.finalImportance); Loading Loading @@ -570,6 +580,7 @@ public class NotificationUsageStats { previous.numAlertViolations = numAlertViolations; previous.numQuotaViolations = numQuotaViolations; previous.numImagesRemoved = numImagesRemoved; previous.numTooOld = numTooOld; noisyImportance.update(previous.noisyImportance); quietImportance.update(previous.quietImportance); finalImportance.update(previous.finalImportance); Loading Loading @@ -679,6 +690,8 @@ public class NotificationUsageStats { output.append("numQuotaViolations=").append(numQuotaViolations).append("\n"); output.append(indentPlusTwo); output.append("numImagesRemoved=").append(numImagesRemoved).append("\n"); output.append(indentPlusTwo); output.append("numTooOld=").append(numTooOld).append("\n"); output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n"); output.append(indentPlusTwo).append(quietImportance.toString()).append("\n"); output.append(indentPlusTwo).append(finalImportance.toString()).append("\n"); Loading Loading @@ -725,6 +738,7 @@ public class NotificationUsageStats { maybePut(dump, "notificationEnqueueRate", getEnqueueRate()); maybePut(dump, "numAlertViolations", numAlertViolations); maybePut(dump, "numImagesRemoved", numImagesRemoved); maybePut(dump, "numTooOld", numTooOld); noisyImportance.maybePut(dump, previous.noisyImportance); quietImportance.maybePut(dump, previous.quietImportance); finalImportance.maybePut(dump, previous.finalImportance); Loading
services/core/java/com/android/server/notification/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -135,3 +135,10 @@ flag { description: "This flag controls which signal is used to handle a user switch system event" bug: "337077643" } flag { name: "reject_old_notifications" namespace: "systemui" description: "This flag does not allow notifications older than 2 weeks old to be posted" bug: "339833083" } No newline at end of file
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +162 −106 Original line number Diff line number Diff line Loading @@ -112,6 +112,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS; import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL; Loading Loading @@ -339,6 +340,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; Loading Loading @@ -909,7 +911,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } mService.clearNotifications(); TestableLooper.get(this).processAllMessages(); if (mTestableLooper != null) { mTestableLooper.processAllMessages(); } try { mService.onDestroy(); Loading @@ -920,14 +924,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); if (mWorkerHandler != null) { // Remove scheduled messages that would be processed when the test is already done, and // could cause issues, for example, messages that remove/cancel shown toasts (this causes // problematic interactions with mocks when they're no longer working as expected). mWorkerHandler.removeCallbacksAndMessages(null); } if (TestableLooper.get(this) != null) { if (mTestableLooper != null) { // Must remove static reference to this test object to prevent leak (b/261039202) TestableLooper.remove(this); mTestableLooper.remove(this); } } Loading Loading @@ -1009,8 +1015,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } public void waitForIdle() { if (mTestableLooper != null) { mTestableLooper.processAllMessages(); } } private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled, int pkgPref, boolean channelEnabled) { Loading Loading @@ -1302,6 +1310,106 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return nrSummary; } private NotificationRecord createAndPostCallStyleNotification(String packageName, UserHandle userHandle, String testName) throws Exception { Person person = new Person.Builder().setName("caller").build(); Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setFlag(FLAG_USER_INITIATED_JOB, true) .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, testName, mUid, 0, nb.build(), userHandle, null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); return mService.findNotificationLocked( packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); } private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); return mService.findNotificationLocked( mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); } private static <T extends Parcelable> T parcelAndUnparcel(T source, Parcelable.Creator<T> creator) { Parcel parcel = Parcel.obtain(); source.writeToParcel(parcel, 0); parcel.setDataPosition(0); return creator.createFromParcel(parcel); } private PendingIntent createPendingIntent(String action) { return PendingIntent.getActivity(mContext, 0, new Intent(action).setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE); } private void allowTestPackageToToast() throws Exception { assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); mService.isSystemUid = false; mService.isSystemAppId = false; setToastRateIsWithinQuota(true); setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); // package is not suspended when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) .thenReturn(false); } private boolean enqueueToast(String testPackage, ITransientNotification callback) throws RemoteException { return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback); } private boolean enqueueToast(INotificationManager service, String testPackage, IBinder token, ITransientNotification callback) throws RemoteException { return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, int displayId) throws RemoteException { return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text, TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null); } private void mockIsVisibleBackgroundUsersSupported(boolean supported) { when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); } private void mockIsUserVisible(int displayId, boolean visible) { when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); } private void mockDisplayAssignedToUser(int displayId) { when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); } private void verifyToastShownForTestPackage(String text, int displayId) { verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), eq(TOAST_DURATION), any(), eq(displayId)); } @Test @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL) public void testLimitTimeOutBroadcast() { Loading Loading @@ -14069,11 +14177,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); original.when = 111; original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); assertThat(mService.mNotificationList.get(0).getNotification().when) .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) Loading @@ -14081,7 +14190,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); update.when = 222; update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); Loading @@ -14090,18 +14199,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when); } @Test public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); original.when = 111; original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); assertThat(mService.mNotificationList.get(0).getNotification().when) .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) Loading @@ -14109,7 +14219,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); update.when = 222; update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); Loading @@ -14118,7 +14228,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats, never()).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old assertThat(mService.mNotificationList.get(0).getNotification().when) .isEqualTo(original.when); // old } @Test Loading Loading @@ -15483,103 +15594,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.getTimeoutAfter()).isEqualTo(20); } private NotificationRecord createAndPostCallStyleNotification(String packageName, UserHandle userHandle, String testName) throws Exception { Person person = new Person.Builder().setName("caller").build(); Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setFlag(FLAG_USER_INITIATED_JOB, true) .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, testName, mUid, 0, nb.build(), userHandle, null, 0); @Test @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) public void testRejectOldNotification_oldWhen() throws Exception { Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis()) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); return mService.findNotificationLocked( packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); } private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); return mService.findNotificationLocked( mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); } private static <T extends Parcelable> T parcelAndUnparcel(T source, Parcelable.Creator<T> creator) { Parcel parcel = Parcel.obtain(); source.writeToParcel(parcel, 0); parcel.setDataPosition(0); return creator.createFromParcel(parcel); } private PendingIntent createPendingIntent(String action) { return PendingIntent.getActivity(mContext, 0, new Intent(action).setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE); } private void allowTestPackageToToast() throws Exception { assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); mService.isSystemUid = false; mService.isSystemAppId = false; setToastRateIsWithinQuota(true); setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); // package is not suspended when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) .thenReturn(false); } private boolean enqueueToast(String testPackage, ITransientNotification callback) throws RemoteException { return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback); } private boolean enqueueToast(INotificationManager service, String testPackage, IBinder token, ITransientNotification callback) throws RemoteException { return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); } private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, int displayId) throws RemoteException { return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text, TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null); assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isFalse(); } private void mockIsVisibleBackgroundUsersSupported(boolean supported) { when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); } @Test @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) public void testRejectOldNotification_mediumOldWhen() throws Exception { Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis()) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); private void mockIsUserVisible(int displayId, boolean visible) { when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isTrue(); } private void mockDisplayAssignedToUser(int displayId) { when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); } @Test @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) public void testRejectOldNotification_zeroWhen() throws Exception { Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setWhen(0) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); private void verifyToastShownForTestPackage(String text, int displayId) { verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), eq(TOAST_DURATION), any(), eq(displayId)); assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isTrue(); } }