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

Commit 55931c47 authored by Jan Tomljanovic's avatar Jan Tomljanovic
Browse files

Stop rate-limiting toasts while the package is in the foreground.

Bug: 185107358
Test: atest NotificationManagerServiceTest
Change-Id: I53b8ec8929c87f31fb948a17f7ff6876aa7a6858
parent bcc5b4c1
Loading
Loading
Loading
Loading
+45 −34
Original line number Diff line number Diff line
@@ -378,8 +378,6 @@ public class NotificationManagerService extends SystemService {
    static final int INVALID_UID = -1;
    static final String ROOT_PKG = "root";

    static final boolean ENABLE_BLOCKED_TOASTS = true;

    static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
            Adjustment.KEY_CONTEXTUAL_ACTIONS,
            Adjustment.KEY_TEXT_REPLIES,
@@ -3035,34 +3033,11 @@ public class NotificationManagerService extends SystemService {
            }

            final int callingUid = Binder.getCallingUid();
            final UserHandle callingUser = Binder.getCallingUserHandle();
            checkCallerIsSameApp(pkg);
            final boolean isSystemToast = isCallerSystemOrPhone()
                    || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
            final boolean isPackageSuspended = isPackagePaused(pkg);
            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
                    callingUid);

            final boolean appIsForeground;
            final long callingIdentity = Binder.clearCallingIdentity();
            try {
                appIsForeground = mActivityManager.getUidImportance(callingUid)
                        == IMPORTANCE_FOREGROUND;
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
                    && !appIsForeground) || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended ? " due to package suspended."
                        : " by user request."));
                return;
            }

            boolean isAppRenderedToast = (callback != null);
            if (blockToast(callingUid, isSystemToast, isAppRenderedToast)) {
                Slog.w(TAG, "Blocking custom toast from package " + pkg
                        + " due to package not in the foreground at time the toast was posted");
            if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) {
                return;
            }

@@ -3116,6 +3091,39 @@ public class NotificationManagerService extends SystemService {
            }
        }

        private boolean checkCanEnqueueToast(String pkg, int callingUid,
                boolean isAppRenderedToast, boolean isSystemToast) {
            final boolean isPackageSuspended = isPackagePaused(pkg);
            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
                    callingUid);

            final boolean appIsForeground;
            final long callingIdentity = Binder.clearCallingIdentity();
            try {
                appIsForeground = mActivityManager.getUidImportance(callingUid)
                        == IMPORTANCE_FOREGROUND;
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }

            if (!isSystemToast && ((notificationsDisabledForPackage && !appIsForeground)
                    || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended ? " due to package suspended."
                        : " by user request."));
                return false;
            }

            if (blockToast(callingUid, isSystemToast, isAppRenderedToast,
                    isPackageInForegroundForToast(callingUid))) {
                Slog.w(TAG, "Blocking custom toast from package " + pkg
                        + " due to package not in the foreground at time the toast was posted");
                return false;
            }

            return true;
        }

        @Override
        public void cancelToast(String pkg, IBinder token) {
            Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);
@@ -7540,12 +7548,13 @@ public class NotificationManagerService extends SystemService {
            boolean isWithinQuota =
                    mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
                            || isExemptFromRateLimiting(record.pkg, userId);
            boolean isPackageInForeground = isPackageInForegroundForToast(record.uid);

            if (tryShowToast(
                    record, rateLimitingEnabled, isWithinQuota)) {
                    record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
                scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                mIsCurrentToastShown = true;
                if (rateLimitingEnabled) {
                if (rateLimitingEnabled && !isPackageInForeground) {
                    mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
                }
                return;
@@ -7561,14 +7570,15 @@ public class NotificationManagerService extends SystemService {

    /** Returns true if it successfully showed the toast. */
    private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
            boolean isWithinQuota) {
        if (rateLimitingEnabled && !isWithinQuota) {
            boolean isWithinQuota, boolean isPackageInForeground) {
        if (rateLimitingEnabled && !isWithinQuota && !isPackageInForeground) {
            reportCompatRateLimitingToastsChange(record.uid);
            Slog.w(TAG, "Package " + record.pkg + " is above allowed toast quota, the "
                    + "following toast was blocked and discarded: " + record);
            return false;
        }
        if (blockToast(record.uid, record.isSystemToast, record.isAppRendered())) {
        if (blockToast(record.uid, record.isSystemToast, record.isAppRendered(),
                isPackageInForeground)) {
            Slog.w(TAG, "Blocking custom toast from package " + record.pkg
                    + " due to package not in the foreground at the time of showing the toast");
            return false;
@@ -7759,10 +7769,11 @@ public class NotificationManagerService extends SystemService {
     * with targetSdk < R. For apps with targetSdk R+, text toasts are not app-rendered, so
     * isAppRenderedToast == true means it's a custom toast.
     */
    private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast) {
    private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast,
            boolean isPackageInForeground) {
        return isAppRenderedToast
                && !isSystemToast
                && !isPackageInForegroundForToast(uid)
                && !isPackageInForeground
                && CompatChanges.isChangeEnabled(CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, uid);
    }

+29 −27
Original line number Diff line number Diff line
@@ -58,7 +58,6 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;

@@ -5037,7 +5036,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    }

    @Test
    public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
    public void testToastRateLimiterWontPreventShowCallForCustomToastWhenInForeground()
            throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;
@@ -5055,30 +5055,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        INotificationManager nmService = (INotificationManager) mService.mService;

        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
        verify(callback, times(0)).show(any());
    }

    @Test
    public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;
        setToastRateIsWithinQuota(false); // rate limit reached
        // Avoids rate limiting.
        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);

        // package is not suspended
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
                .thenReturn(false);

        setAppInForegroundForToasts(mUid, true);

        Binder token = new Binder();
        ITransientNotification callback = mock(ITransientNotification.class);
        INotificationManager nmService = (INotificationManager) mService.mService;

        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
        verify(callback).show(any());
        verify(callback, times(1)).show(any());
    }

    @Test
@@ -5186,12 +5163,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    }

    @Test
    public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
    public void testToastRateLimiterCanPreventShowCallForTextToast_whenInBackground()
            throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;
        setToastRateIsWithinQuota(false); // rate limit reached
        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
        setAppInForegroundForToasts(mUid, false);

        // package is not suspended
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5205,6 +5184,28 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
    }

    @Test
    public void testToastRateLimiterWontPreventShowCallForTextToast_whenInForeground()
            throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;
        setToastRateIsWithinQuota(false); // rate limit reached
        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
        setAppInForegroundForToasts(mUid, true);

        // package is not suspended
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
                .thenReturn(false);

        Binder token = new Binder();
        INotificationManager nmService = (INotificationManager) mService.mService;

        nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
        verify(mStatusBar, times(1))
                .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
    }

    @Test
    public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
        final String testPackage = "testPackageName";
@@ -5212,6 +5213,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        mService.isSystemUid = false;
        setToastRateIsWithinQuota(false); // rate limit reached
        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
        setAppInForegroundForToasts(mUid, false);

        // package is not suspended
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))