diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 20ad87a096a0a..e2ff0ad5c0c19 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -261,6 +261,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import libcore.io.IoUtils; @@ -391,6 +392,7 @@ public class NotificationManagerService extends SystemService { private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L; private IActivityManager mAm; + private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; @@ -1858,10 +1860,10 @@ public class NotificationManagerService extends SystemService { ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am, - UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm, - IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, - UserManager userManager, NotificationHistoryManager historyManager, - StatsManager statsManager) { + ActivityTaskManagerInternal atm, UsageStatsManagerInternal appUsageStats, + DevicePolicyManagerInternal dpm, IUriGrantsManager ugm, + UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager, + NotificationHistoryManager historyManager, StatsManager statsManager) { mHandler = handler; Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), @@ -1871,6 +1873,7 @@ public class NotificationManagerService extends SystemService { mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); mAm = am; + mAtm = atm; mUgm = ugm; mUgmInternal = ugmInternal; mPackageManager = packageManager; @@ -2057,6 +2060,7 @@ public class NotificationManagerService extends SystemService { systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper(), ActivityManager.getService(), + LocalServices.getService(ActivityTaskManagerInternal.class), LocalServices.getService(UsageStatsManagerInternal.class), LocalServices.getService(DevicePolicyManagerInternal.class), UriGrantsManager.getService(), @@ -2793,7 +2797,9 @@ public class NotificationManagerService extends SystemService { return; } - if (callback != null && !appIsForeground && !isSystemToast && isCustom) { + boolean isAppRenderedToast = (callback != null); + if (isAppRenderedToast && isCustom && !isSystemToast + && !isPackageInForegroundForToast(pkg, callingUid)) { boolean block; long id = Binder.clearCallingIdentity(); try { @@ -2871,6 +2877,28 @@ public class NotificationManagerService extends SystemService { } } + /** + * Implementation note: Our definition of foreground for toasts is an implementation matter + * and should strike a balance between functionality and anti-abuse effectiveness. We + * currently worry about the following cases: + *
    + *
  1. App with fullscreen activity: Allow toasts + *
  2. App behind translucent activity from other app: Block toasts + *
  3. App in multi-window: Allow toasts + *
  4. App with expanded bubble: Allow toasts + *
  5. App posting toasts on onCreate(), onStart(), onResume(): Allow toasts + *
  6. App posting toasts on onPause(), onStop(), onDestroy(): Block toasts + *
+ * Checking if the UID has any resumed activities satisfy use-cases above. + * + *

Checking if {@code mActivityManager.getUidImportance(callingUid) == + * IMPORTANCE_FOREGROUND} does not work because it considers the app in foreground if it has + * any visible activities, failing case 2 in list above. + */ + private boolean isPackageInForegroundForToast(String pkg, int callingUid) { + return mAtm.hasResumedActivity(callingUid); + } + @Override public void cancelToast(String pkg, IBinder token) { Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index ca856ca7c1e6a..edc87e5a4d889 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -168,6 +168,11 @@ public abstract class ActivityTaskManagerInternal { */ public abstract List getTopVisibleActivities(); + /** + * Returns whether {@code uid} has any resumed activity. + */ + public abstract boolean hasResumedActivity(int uid); + /** * Notify listeners that contents are drawn for the first time on a single task display. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7bacc427feb83..890b9454e6b64 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6171,6 +6171,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public boolean hasResumedActivity(int uid) { + synchronized (mGlobalLock) { + final ArraySet processes = mProcessMap.getProcesses(uid); + for (int i = 0, n = processes.size(); i < n; i++) { + final WindowProcessController process = processes.valueAt(i); + if (process.hasResumedActivity()) { + return true; + } + } + } + return false; + } + @Override public int startActivitiesAsPackage(String packageName, @Nullable String featureId, int userId, Intent[] intents, Bundle bOptions) { diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index f356329cbefb3..194ed3ec3b0ee 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -751,6 +751,16 @@ public class WindowProcessController extends ConfigurationContainer= 0; --i) { + final ActivityRecord activity = mActivities.get(i); + if (activity.isState(RESUMED)) { + return true; + } + } + return false; + } + void updateIntentForHeavyWeightActivity(Intent intent) { if (mActivities.isEmpty()) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f179840fba113..2b8ce782fe163 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -17,7 +17,6 @@ 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.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_AUTO_CANCEL; @@ -165,6 +164,7 @@ import com.android.server.notification.NotificationManagerService.NotificationAs import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; @@ -253,6 +253,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock IActivityManager mAm; @Mock + ActivityTaskManagerInternal mAtm; + @Mock IUriGrantsManager mUgm; @Mock UriGrantsManagerInternal mUgmInternal; @@ -454,7 +456,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, - mGroupHelper, mAm, mAppUsageStats, + mGroupHelper, mAm, mAtm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mUm, mHistoryManager, mStatsManager); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); @@ -4704,8 +4706,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); - // this app is in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND); + setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4723,8 +4724,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) .thenReturn(false); - // this app is NOT in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_NONE); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> no toasts enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4742,8 +4742,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) .thenReturn(false); - // this app is in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND); + setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), @@ -4761,8 +4760,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) .thenReturn(false); - // this app is NOT in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_NONE); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> toast should still enqueue ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), @@ -4820,8 +4818,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> no toasts enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4843,8 +4840,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> system toast can still be enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4852,6 +4848,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, mService.mToastQueue.size()); } + private void setAppInForegroundForToasts(int uid, boolean inForeground) { + int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE; + when(mActivityManager.getUidImportance(mUid)).thenReturn(importance); + when(mAtm.hasResumedActivity(uid)).thenReturn(inForeground); + } + @Test public void testOnPanelRevealedAndHidden() { int items = 5; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index e4d50c0e17869..19ff683a46a11 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -65,6 +65,7 @@ import com.android.server.lights.LightsManager; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; @@ -147,6 +148,7 @@ public class RoleObserverTest extends UiServiceTestCase { mock(SnoozeHelper.class), mock(NotificationUsageStats.class), mock(AtomicFile.class), mock(ActivityManager.class), mock(GroupHelper.class), mock(IActivityManager.class), + mock(ActivityTaskManagerInternal.class), mock(UsageStatsManagerInternal.class), mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class), mock(UriGrantsManagerInternal.class),