If the calling process isn't whitelisted for background
activity start, check the other processes of calling UID
too

Since looking at whitelisted processes can be computationally
more expensive than other checks in the barrier, move them
to be last - if callingUid is allowed to open bg activity via
other means, like permission, we'll bail quicker, especially
for apps like gmscore.

Bug: 129853314
Test: atest WmTests:ActivityStarterTests
Test: atest BackgroundActivityLaunchTest
Test: atest CtsActivityManagerDeviceTestCases:ActivityStarterTests
Test: atest WmTests:WindowProcessControllerMapTests
Test: manual (repro the quoted bug, and see that while
      com.google.android.googlequicksearchbox:search isn't
      whitelisted, AGSA had other whitelisting process,
      com.google.android.googlequicksearchbox:interactor)
Change-Id: Ic7d6c12e76cb707f06e0f216c9958d5d2f21a840
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8957444..c8fcb9e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -980,31 +980,6 @@
                 return false;
             }
         }
-        // If we don't have callerApp at this point, no caller was provided to startActivity().
-        // That's the case for PendingIntent-based starts, since the creator's process might not be
-        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
-        // caller, so that we can make the decision based on its foreground/whitelisted state.
-        if (callerApp == null) {
-            callerApp = mService.getProcessController(realCallingPid, realCallingUid);
-        }
-        if (callerApp != null) {
-            // don't abort if the callerApp is instrumenting with background activity starts privs
-            if (callerApp.isInstrumentingWithBackgroundActivityStartPrivileges()) {
-                return false;
-            }
-            // don't abort if the caller is currently temporarily whitelisted
-            if (callerApp.areBackgroundActivityStartsAllowed()) {
-                return false;
-            }
-            // don't abort if the caller has an activity in any foreground task
-            if (callerApp.hasActivityInVisibleTask()) {
-                return false;
-            }
-            // don't abort if the caller is bound by a UID that's currently foreground
-            if (isBoundByForegroundUid(callerApp)) {
-                return false;
-            }
-        }
         // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
         if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                 == PERMISSION_GRANTED) {
@@ -1029,6 +1004,33 @@
                     + " temporarily whitelisted. This will not be supported in future Q builds.");
             return false;
         }
+        // If we don't have callerApp at this point, no caller was provided to startActivity().
+        // That's the case for PendingIntent-based starts, since the creator's process might not be
+        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
+        // caller, so that we can make the decision based on its foreground/whitelisted state.
+        int callerAppUid = callingUid;
+        if (callerApp == null) {
+            callerApp = mService.getProcessController(realCallingPid, realCallingUid);
+            callerAppUid = realCallingUid;
+        }
+        // don't abort if the callerApp or other processes of that uid are whitelisted in any way
+        if (callerApp != null) {
+            // first check the original calling process
+            if (callerApp.areBackgroundActivityStartsAllowed()) {
+                return false;
+            }
+            // only if that one wasn't whitelisted, check the other ones
+            final ArraySet<WindowProcessController> uidProcesses =
+                    mService.mProcessMap.getProcesses(callerAppUid);
+            if (uidProcesses != null) {
+                for (int i = uidProcesses.size() - 1; i >= 0; i--) {
+                    final WindowProcessController proc = uidProcesses.valueAt(i);
+                    if (proc != callerApp && proc.areBackgroundActivityStartsAllowed()) {
+                        return false;
+                    }
+                }
+            }
+        }
         // anything that has fallen through would currently be aborted
         Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
@@ -1053,17 +1055,6 @@
         return true;
     }
 
-    private boolean isBoundByForegroundUid(WindowProcessController callerApp) {
-        final ArraySet<Integer> boundClientUids = callerApp.getBoundClientUids();
-        for (int i = boundClientUids.size() - 1; i >= 0; --i) {
-            final int uid = boundClientUids.valueAt(i);
-            if (mService.isUidForeground(uid)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     // TODO: remove this toast after feature development is done
     void showBackgroundActivityBlockedToast(boolean abort, String callingPackage) {
         final Resources res = mService.mContext.getResources();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 76774d7..3fa0268 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -373,8 +373,8 @@
     private final SparseArray<String> mPendingTempWhitelist = new SparseArray<>();
     /** All processes currently running that might have a window organized by name. */
     final ProcessMap<WindowProcessController> mProcessNames = new ProcessMap<>();
-    /** All processes we currently have running mapped by pid */
-    final SparseArray<WindowProcessController> mPidMap = new SparseArray<>();
+    /** All processes we currently have running mapped by pid and uid */
+    final WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
     /** This is the process holding what we currently consider to be the "home" activity. */
     WindowProcessController mHomeProcess;
     /** The currently running heavy-weight process, if any. */
@@ -913,7 +913,7 @@
             return getGlobalConfiguration();
         }
         synchronized (mGlobalLock) {
-            final WindowProcessController app = mPidMap.get(pid);
+            final WindowProcessController app = mProcessMap.getProcess(pid);
             return app != null ? app.getConfiguration() : getGlobalConfiguration();
         }
     }
@@ -4640,7 +4640,7 @@
         enforceSystemHasVrFeature();
         synchronized (mGlobalLock) {
             final int pid = Binder.getCallingPid();
-            final WindowProcessController wpc = mPidMap.get(pid);
+            final WindowProcessController wpc = mProcessMap.getProcess(pid);
             mVrController.setVrThreadLocked(tid, pid, wpc);
         }
     }
@@ -4659,7 +4659,7 @@
         enforceSystemHasVrFeature();
         synchronized (mGlobalLock) {
             final int pid = Binder.getCallingPid();
-            final WindowProcessController proc = mPidMap.get(pid);
+            final WindowProcessController proc = mProcessMap.getProcess(pid);
             mVrController.setPersistentVrThreadLocked(tid, pid, proc);
         }
     }
@@ -5204,9 +5204,10 @@
             mH.sendMessage(msg);
         }
 
-        for (int i = mPidMap.size() - 1; i >= 0; i--) {
-            final int pid = mPidMap.keyAt(i);
-            final WindowProcessController app = mPidMap.get(pid);
+        SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
+        for (int i = pidMap.size() - 1; i >= 0; i--) {
+            final int pid = pidMap.keyAt(i);
+            final WindowProcessController app = pidMap.get(pid);
             if (DEBUG_CONFIGURATION) {
                 Slog.v(TAG_CONFIGURATION, "Update process config of "
                         + app.mName + " to new config " + configCopy);
@@ -5859,7 +5860,7 @@
     }
 
     WindowProcessController getProcessController(int pid, int uid) {
-        final WindowProcessController proc = mPidMap.get(pid);
+        final WindowProcessController proc = mProcessMap.getProcess(pid);
         if (proc == null) return null;
         if (UserHandle.isApp(uid) && proc.mUid == uid) {
             return proc;
@@ -6423,14 +6424,14 @@
         @Override
         public void onProcessMapped(int pid, WindowProcessController proc) {
             synchronized (mGlobalLock) {
-                mPidMap.put(pid, proc);
+                mProcessMap.put(pid, proc);
             }
         }
 
         @Override
         public void onProcessUnMapped(int pid) {
             synchronized (mGlobalLock) {
-                mPidMap.remove(pid);
+                mProcessMap.remove(pid);
             }
         }
 
@@ -6503,7 +6504,7 @@
                     }
                     return;
                 }
-                final WindowProcessController process = mPidMap.get(pid);
+                final WindowProcessController process = mProcessMap.getProcess(pid);
                 if (process == null) {
                     if (DEBUG_CONFIGURATION) {
                         Slog.w(TAG, "Trying to update display configuration for invalid "
@@ -6696,7 +6697,7 @@
                     // Only allow this from foreground processes, so that background
                     // applications can't abuse it to prevent system UI from being shown.
                     if (uid >= FIRST_APPLICATION_UID) {
-                        final WindowProcessController proc = mPidMap.get(pid);
+                        final WindowProcessController proc = mProcessMap.getProcess(pid);
                         if (!proc.isPerceptible()) {
                             Slog.w(TAG, "Ignoring closeSystemDialogs " + reason
                                     + " from background process " + proc);
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index c8f8e82..104805f 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -48,6 +48,7 @@
 import android.os.RemoteException;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.Xml;
 
 public final class CompatModePackages {
@@ -324,8 +325,9 @@
             ActivityRecord starting = stack.restartPackage(packageName);
 
             // Tell all processes that loaded this package about the change.
-            for (int i = mService.mPidMap.size() - 1; i >= 0; i--) {
-                final WindowProcessController app = mService.mPidMap.valueAt(i);
+            SparseArray<WindowProcessController> pidMap = mService.mProcessMap.getPidMap();
+            for (int i = pidMap.size() - 1; i >= 0; i--) {
+                final WindowProcessController app = pidMap.valueAt(i);
                 if (!app.mPkgList.contains(packageName)) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 4ca35f7..eb919eb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -372,18 +372,39 @@
         mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
     }
 
-    public boolean areBackgroundActivityStartsAllowed() {
-        return mAllowBackgroundActivityStarts;
+    boolean areBackgroundActivityStartsAllowed() {
+        // allow if the whitelisting flag was explicitly set
+        if (mAllowBackgroundActivityStarts) {
+            return true;
+        }
+        // allow if the proc is instrumenting with background activity starts privs
+        if (mInstrumentingWithBackgroundActivityStartPrivileges) {
+            return true;
+        }
+        // allow if the caller has an activity in any foreground task
+        if (hasActivityInVisibleTask()) {
+            return true;
+        }
+        // allow if the caller is bound by a UID that's currently foreground
+        if (isBoundByForegroundUid()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isBoundByForegroundUid() {
+        for (int i = mBoundClientUids.size() - 1; i >= 0; --i) {
+            if (mAtm.isUidForeground(mBoundClientUids.valueAt(i))) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public void setBoundClientUids(ArraySet<Integer> boundClientUids) {
         mBoundClientUids = boundClientUids;
     }
 
-    public ArraySet<Integer> getBoundClientUids() {
-        return mBoundClientUids;
-    }
-
     public void setInstrumenting(boolean instrumenting,
             boolean hasBackgroundActivityStartPrivileges) {
         mInstrumenting = instrumenting;
@@ -394,14 +415,6 @@
         return mInstrumenting;
     }
 
-    /**
-     * @return true if the instrumentation was started by a holder of
-     * START_ACTIVITIES_FROM_BACKGROUND permission
-     */
-    boolean isInstrumentingWithBackgroundActivityStartPrivileges() {
-        return mInstrumentingWithBackgroundActivityStartPrivileges;
-    }
-
     public void setPerceptible(boolean perceptible) {
         mPerceptible = perceptible;
     }
@@ -487,7 +500,7 @@
         }
     }
 
-    boolean hasActivityInVisibleTask() {
+    private boolean hasActivityInVisibleTask() {
         for (int i = mActivities.size() - 1; i >= 0; --i) {
             TaskRecord task = mActivities.get(i).getTaskRecord();
             if (task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
new file mode 100644
index 0000000..2767972
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import java.util.Map;
+import java.util.HashMap;
+
+final class WindowProcessControllerMap {
+
+    /** All processes we currently have running mapped by pid */
+    private final SparseArray<WindowProcessController> mPidMap = new SparseArray<>();
+    /** All processes we currently have running mapped by uid */
+    private final Map<Integer, ArraySet<WindowProcessController>> mUidMap = new HashMap<>();
+
+    /** Retrieves a currently running process for pid. */
+    WindowProcessController getProcess(int pid) {
+        return mPidMap.get(pid);
+    }
+
+    /** Retrieves all currently running processes for uid. */
+    ArraySet<WindowProcessController> getProcesses(int uid) {
+        return mUidMap.get(uid);
+    }
+
+    SparseArray<WindowProcessController> getPidMap() {
+        return mPidMap;
+    }
+
+    void put(int pid, WindowProcessController proc) {
+        // if there is a process for this pid already in mPidMap it'll get replaced automagically,
+        // but we actually need to remove it from mUidMap too before adding the new one
+        final WindowProcessController prevProc = mPidMap.get(pid);
+        if (prevProc != null) {
+            removeProcessFromUidMap(prevProc);
+        }
+        // put process into mPidMap
+        mPidMap.put(pid, proc);
+        // put process into mUidMap
+        final int uid = proc.mUid;
+        ArraySet<WindowProcessController> procSet = mUidMap.getOrDefault(uid,
+                new ArraySet<WindowProcessController>());
+        procSet.add(proc);
+        mUidMap.put(uid, procSet);
+    }
+
+    void remove(int pid) {
+        final WindowProcessController proc = mPidMap.get(pid);
+        if (proc != null) {
+            // remove process from mPidMap
+            mPidMap.remove(pid);
+            // remove process from mUidMap
+            removeProcessFromUidMap(proc);
+        }
+    }
+
+    private void removeProcessFromUidMap(WindowProcessController proc) {
+        if (proc == null) {
+            return;
+        }
+        final int uid = proc.mUid;
+        ArraySet<WindowProcessController> procSet = mUidMap.get(uid);
+        if (procSet != null) {
+            procSet.remove(proc);
+            if (procSet.isEmpty()) {
+                mUidMap.remove(uid);
+            }
+        }
+    }
+}