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

Commit 58b2453e authored by Beverly's avatar Beverly
Browse files

Always allow toasts from foreground apps

If the app is in foreground, allow the app to post
toasts even when notifications are blocked for that app.

Also added tests for when Toasts should/shouldn't show.

Test: atest frameworks/base/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
Change-Id: I4f14b313d9b751c5ad00d309462aee6460cddad0
Fixes: 71546399
parent f9151368
Loading
Loading
Loading
Loading
+25 −14
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.server.notification;
package com.android.server.notification;


import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -2001,7 +2002,8 @@ public class NotificationManagerService extends SystemService {
        return mInternalService;
        return mInternalService;
    }
    }


    private final IBinder mService = new INotificationManager.Stub() {
    @VisibleForTesting
    final IBinder mService = new INotificationManager.Stub() {
        // Toasts
        // Toasts
        // ============================================================================
        // ============================================================================


@@ -2015,22 +2017,31 @@ public class NotificationManagerService extends SystemService {
            }
            }


            if (pkg == null || callback == null) {
            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback);
                return ;
                return ;
            }
            }
            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());


            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
            final int callingUid = Binder.getCallingUid();
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
            final boolean isSystemToast = isCallerSystemOrPhone()
                            || isPackageSuspended)) {
                    || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
            final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
                    callingUid);

            long callingIdentity = Binder.clearCallingIdentity();
            try {
                final boolean appIsForeground = mActivityManager.getUidImportance(callingUid)
                        == IMPORTANCE_FOREGROUND;
                if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
                        && !appIsForeground) || isPackageSuspended)) {
                    Slog.e(TAG, "Suppressing toast from package " + pkg
                    Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                            + (isPackageSuspended ? " due to package suspended."
                                ? " due to package suspended by administrator."
                            : " by user request."));
                            : " by user request."));
                    return;
                    return;
                }
                }
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }


            synchronized (mToastQueue) {
            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                int callingPid = Binder.getCallingPid();
+109 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package com.android.server.notification;
package com.android.server.notification;


import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -74,6 +76,7 @@ import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.app.ITransientNotification;
import android.app.IUriGrantsManager;
import android.app.IUriGrantsManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
@@ -118,12 +121,14 @@ import android.util.Log;
import com.android.internal.R;
import com.android.internal.R;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.server.LocalServices;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiServiceTestCase;
import com.android.server.UiServiceTestCase;
import com.android.server.lights.Light;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LightsManager;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;


import org.junit.After;
import org.junit.After;
import org.junit.Before;
import org.junit.Before;
@@ -160,6 +165,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    private IPackageManager mPackageManager;
    private IPackageManager mPackageManager;
    @Mock
    @Mock
    private PackageManager mPackageManagerClient;
    private PackageManager mPackageManagerClient;
    @Mock
    private WindowManagerInternal mWindowManagerInternal;
    private TestableContext mContext = spy(getContext());
    private TestableContext mContext = spy(getContext());
    private final String PKG = mContext.getPackageName();
    private final String PKG = mContext.getPackageName();
    private TestableLooper mTestableLooper;
    private TestableLooper mTestableLooper;
@@ -238,6 +245,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        }
        }
    }
    }


    private class TestableToastCallback extends ITransientNotification.Stub {
        @Override
        public void show(IBinder windowToken) {
        }

        @Override
        public void hide() {
        }
    }

    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
@@ -249,6 +266,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {


        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
        LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
        LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
        LocalServices.removeServiceForTest(WindowManagerInternal.class);
        LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);


        mService = new TestableNotificationManagerService(mContext);
        mService = new TestableNotificationManagerService(mContext);


@@ -302,6 +321,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                    mGroupHelper, mAm, mAppUsageStats,
                    mGroupHelper, mAm, mAppUsageStats,
                    mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                    mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                    mAppOpsManager);
                    mAppOpsManager);
            mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
        } catch (SecurityException e) {
        } catch (SecurityException e) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                throw e;
                throw e;
@@ -3531,4 +3551,93 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {


        assertEquals(0, captor.getValue().getNotification().flags);
        assertEquals(0, captor.getValue().getNotification().flags);
    }
    }

    @Test
    public void testAllowForegroundToasts() throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;

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

        // notifications from this package are blocked by the user
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);

        // this app is in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND);

        // enqueue toast -> toast should still enqueue
        ((INotificationManager)mService.mService).enqueueToast(testPackage,
                new TestableToastCallback(), 2000, 0);
        assertEquals(1, mService.mToastQueue.size());
    }

    @Test
    public void testDisallowToastsFromSuspendedPackages() throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;

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

        // notifications from this package are NOT blocked by the user
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_LOW);

        // enqueue toast -> no toasts enqueued
        ((INotificationManager)mService.mService).enqueueToast(testPackage,
                new TestableToastCallback(), 2000, 0);
        assertEquals(0, mService.mToastQueue.size());
    }

    @Test
    public void testDisallowToastsFromBlockedApps() throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = false;

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

        // notifications from this package are blocked by the user
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);

        // this app is NOT in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE);

        // enqueue toast -> no toasts enqueued
        ((INotificationManager)mService.mService).enqueueToast(testPackage,
                new TestableToastCallback(), 2000, 0);
        assertEquals(0, mService.mToastQueue.size());
    }

    @Test
    public void testAlwaysAllowSystemToasts() throws Exception {
        final String testPackage = "testPackageName";
        assertEquals(0, mService.mToastQueue.size());
        mService.isSystemUid = true;

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

        // notifications from this package ARE blocked by the user
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);

        // this app is NOT in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE);

        // enqueue toast -> system toast can still be enqueued
        ((INotificationManager)mService.mService).enqueueToast(testPackage,
                new TestableToastCallback(), 2000, 0);
        assertEquals(1, mService.mToastQueue.size());
    }
}
}