Allow custom toasts only if app has resumed activity

Bug linked is a vulnerability where an app starts an external activity
that it wants the user to interact with along with repeatedly posting
custom toasts with the intent of redressing the UI of the activity just
launched.

The activity that is being started is translucent, so the check for
IMPORTANCE_FOREGROUND wasn't working because activity manager was still
considering the process foreground since it had a visible activity.

Thus, changing our logic to only enable custom toasts in case the app
has a resumed activity. This has the following implications in the
cases below:
* Translucent activity on top: Block toasts
* Multi-window: Allow toasts
* Bubble: Allows when bubble is expanded (it's a resumed activity)
* SAW: Probably block, but fine since app can already do what they want
* onCreate(), onStart(), onResume(): Allow toasts
* onPause(), onStop(), onDestroy(): Block toasts

Note that custom toasts are deprecated and we haven't specified what
exactly "foreground" or "background" meant in this context, so we have
some flexibility in the implementation.

Bug: 115385786
Test: From an app start a translucent activity from another package and
      then try to post a toast, verify the toast is blocked.
Test: atest ToastUITest android.widget.cts.ToastTest
      android.widget.cts29.ToastTest android.server.wm.ToastTest
Change-Id: Ia434332e066f1ef2cd01e150b087f8e5117f1e63
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 69a5b35..0ee7344 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -272,6 +272,7 @@
 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;
@@ -403,6 +404,7 @@
     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;
@@ -1905,10 +1907,10 @@
             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(),
@@ -1918,6 +1920,7 @@
         mAccessibilityManager =
                 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
         mAm = am;
+        mAtm = atm;
         mUgm = ugm;
         mUgmInternal = ugmInternal;
         mPackageManager = packageManager;
@@ -2104,6 +2107,7 @@
                         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(),
@@ -2835,7 +2839,9 @@
                 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 {
@@ -2913,6 +2919,28 @@
             }
         }
 
+        /**
+         * 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:
+         * <ol>
+         *     <li>App with fullscreen activity: Allow toasts
+         *     <li>App behind translucent activity from other app: Block toasts
+         *     <li>App in multi-window: Allow toasts
+         *     <li>App with expanded bubble: Allow toasts
+         *     <li>App posting toasts on onCreate(), onStart(), onResume(): Allow toasts
+         *     <li>App posting toasts on onPause(), onStop(), onDestroy(): Block toasts
+         * </ol>
+         * Checking if the UID has any resumed activities satisfy use-cases above.
+         *
+         * <p>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 ca856ca..edc87e5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -169,6 +169,11 @@
     public abstract List<IBinder> 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.
      *
      * @param displayId An ID of the display on which contents are drawn.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7bacc42..890b945 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6172,6 +6172,20 @@
         }
 
         @Override
+        public boolean hasResumedActivity(int uid) {
+            synchronized (mGlobalLock) {
+                final ArraySet<WindowProcessController> 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) {
             Objects.requireNonNull(intents, "intents");
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 32eb932..e5400d8 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 @@
         return false;
     }
 
+    boolean hasResumedActivity() {
+        for (int i = mActivities.size() - 1; i >= 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 64d481a..6315ba6 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;
@@ -41,8 +40,6 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -167,6 +164,7 @@
 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 +251,8 @@
     @Mock
     IActivityManager mAm;
     @Mock
+    ActivityTaskManagerInternal mAtm;
+    @Mock
     IUriGrantsManager mUgm;
     @Mock
     UriGrantsManagerInternal mUgmInternal;
@@ -442,7 +442,7 @@
                 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);
@@ -4632,8 +4632,7 @@
         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(),
@@ -4651,8 +4650,7 @@
         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(),
@@ -4670,8 +4668,7 @@
         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(),
@@ -4689,8 +4686,7 @@
         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(),
@@ -4748,8 +4744,7 @@
         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(),
@@ -4771,8 +4766,7 @@
         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(),
@@ -4780,6 +4774,12 @@
         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 e4d50c0..19ff683 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.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 @@
                     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),