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:
+ *
+ * - App with fullscreen activity: Allow toasts
+ *
- App behind translucent activity from other app: Block toasts
+ *
- App in multi-window: Allow toasts
+ *
- App with expanded bubble: Allow toasts
+ *
- App posting toasts on onCreate(), onStart(), onResume(): Allow toasts
+ *
- 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),