Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4b9f3fab authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Reject old notifications" into main

parents 30b622a1 7c8cb67c
Loading
Loading
Loading
Loading
+45 −15
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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;
    }
+14 −0
Original line number Diff line number Diff line
@@ -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());
@@ -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;
@@ -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);
@@ -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);
@@ -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");
@@ -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);
+7 −0
Original line number Diff line number Diff line
@@ -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
+162 −106
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -909,7 +911,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        }
        mService.clearNotifications();
        TestableLooper.get(this).processAllMessages();
        if (mTestableLooper != null) {
            mTestableLooper.processAllMessages();
        }
        try {
            mService.onDestroy();
@@ -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);
        }
    }
@@ -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) {
@@ -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() {
@@ -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)))
@@ -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();
@@ -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)))
@@ -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();
@@ -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
@@ -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();
    }
}