Merge "Formalize recents component in the system"
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 251863c..de27b4f 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -102,7 +102,7 @@
     public static final int ACTIVITY_TYPE_STANDARD = 1;
     /** Home/Launcher activity type. */
     public static final int ACTIVITY_TYPE_HOME = 2;
-    /** Recents/Overview activity type. */
+    /** Recents/Overview activity type. There is only one activity with this type in the system. */
     public static final int ACTIVITY_TYPE_RECENTS = 3;
     /** Assistant activity type. */
     public static final int ACTIVITY_TYPE_ASSISTANT = 4;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 571aad9..e5f3aaf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2356,6 +2356,10 @@
          property. If this is false, then the following recents config flags are ignored. -->
     <bool name="config_hasRecents">true</bool>
 
+    <!-- Component name for the activity that will be presenting the Recents UI, which will receive
+         special permissions for API related to fetching and presenting recent tasks. -->
+    <string name="config_recentsComponentName" translatable="false">com.android.systemui/.recents.RecentsActivity</string>
+
     <!-- The minimum number of visible recent tasks to be presented to the user through the
          SystemUI. Can be -1 if there is no minimum limit. -->
     <integer name="config_minNumVisibleRecentTasks_grid">-1</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 896de53..3dff9d7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -311,6 +311,7 @@
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="bool" name="config_hasRecents" />
+  <java-symbol type="string" name="config_recentsComponentName" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 25c2fc9..7442904 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -302,11 +302,6 @@
      */
     private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
             final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
-        if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
-                || windowingMode == WINDOWING_MODE_PINNED) {
-            return null;
-        }
-
         // Calculate the offscreen task rect (for tasks that are not backed by views)
         TaskView taskView = stackView.getChildViewForTask(task);
         TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d3a6719..93c0a39 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.Manifest.permission.REMOVE_TASKS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
@@ -672,7 +673,7 @@
     /**
      * List of intents that were used to start the most recent tasks.
      */
-    final RecentTasks mRecentTasks;
+    private final RecentTasks mRecentTasks;
 
     /**
      * For addAppTask: cached of the last activity component that was added.
@@ -2773,7 +2774,7 @@
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
         mActivityStarter = new ActivityStarter(this);
-        mRecentTasks = new RecentTasks(this, mStackSupervisor);
+        mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
 
@@ -2819,6 +2820,14 @@
         return new ActivityStackSupervisor(this, mHandler.getLooper());
     }
 
+    protected RecentTasks createRecentTasks() {
+        return new RecentTasks(this, mStackSupervisor);
+    }
+
+    RecentTasks getRecentTasks() {
+        return mRecentTasks;
+    }
+
     public void setSystemServiceManager(SystemServiceManager mgr) {
         mSystemServiceManager = mgr;
     }
@@ -3154,6 +3163,7 @@
             synchronized (this) {
                 final ActivityStack stack = mStackSupervisor.getStack(stackId);
                 if (stack == null) {
+                    Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId);
                     return;
                 }
                 final ActivityRecord r = stack.topRunningActivityLocked();
@@ -3190,7 +3200,8 @@
     /** Sets the task stack listener that gets callbacks when a task stack changes. */
     @Override
     public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "registerTaskStackListener()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "registerTaskStackListener()");
         mTaskChangeNotificationController.registerTaskStackListener(listener);
     }
 
@@ -3199,7 +3210,8 @@
      */
     @Override
     public void unregisterTaskStackListener(ITaskStackListener listener) throws RemoteException {
-         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "unregisterTaskStackListener()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "unregisterTaskStackListener()");
          mTaskChangeNotificationController.unregisterTaskStackListener(listener);
      }
 
@@ -4877,12 +4889,9 @@
 
     @Override
     public final int startActivityFromRecents(int taskId, Bundle bOptions) {
-        if (checkCallingPermission(START_TASKS_FROM_RECENTS) != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: startActivityFromRecents called without " +
-                    START_TASKS_FROM_RECENTS;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+                "startActivityFromRecents()");
+
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -8334,9 +8343,6 @@
         }
     }
 
-    /**
-     * This can be called with or without the global lock held.
-     */
     int checkComponentPermission(String permission, int pid, int uid,
             int owningUid, boolean exported) {
         if (pid == MY_PID) {
@@ -8411,6 +8417,15 @@
     }
 
     /**
+     * This can be called with or without the global lock held.
+     */
+    void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+        if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+            enforceCallingPermission(permission, func);
+        }
+    }
+
+    /**
      * Determine if UID is holding permissions required to access {@link Uri} in
      * the given {@link ProviderInfo}. Final permission checking is always done
      * in {@link ContentProvider}.
@@ -9799,6 +9814,11 @@
     }
 
     private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
+        if (mRecentTasks.isCallerRecents(callingUid)) {
+            // Always allow the recents component to get tasks
+            return true;
+        }
+
         boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
         if (!allowed) {
@@ -9846,8 +9866,7 @@
     @Override
     public ActivityManager.TaskDescription getTaskDescription(int id) {
         synchronized (this) {
-            enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                    "getTaskDescription()");
+            enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getTaskDescription()");
             final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
                     MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (tr != null) {
@@ -10041,7 +10060,8 @@
 
     @Override
     public void cancelTaskWindowTransition(int taskId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskWindowTransition()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "cancelTaskWindowTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10060,7 +10080,8 @@
 
     @Override
     public void cancelTaskThumbnailTransition(int taskId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskThumbnailTransition()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "cancelTaskThumbnailTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10079,7 +10100,7 @@
 
     @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
-        enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
+        enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
             final TaskRecord task;
@@ -10132,12 +10153,13 @@
 
     @Override
     public void removeStack(int stackId) {
-        enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
                 final ActivityStack stack = mStackSupervisor.getStack(stackId);
                 if (stack == null) {
+                    Slog.w(TAG, "removeStack: No stack with id=" + stackId);
                     return;
                 }
                 if (!stack.isActivityTypeStandardOrUndefined()) {
@@ -10157,7 +10179,8 @@
      */
     @Override
     public void removeStacksInWindowingModes(int[] windowingModes) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "removeStacksInWindowingModes()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10170,7 +10193,8 @@
 
     @Override
     public void removeStacksWithActivityTypes(int[] activityTypes) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "removeStacksWithActivityTypes()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10199,7 +10223,7 @@
 
     @Override
     public boolean removeTask(int taskId) {
-        enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
+        enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10376,7 +10400,7 @@
 
     @Override
     public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10412,7 +10436,7 @@
 
     @Override
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10463,7 +10487,7 @@
     @Override
     public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
             Rect initialBounds) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10502,12 +10526,16 @@
      */
     @Override
     public void dismissSplitScreenMode(boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 final ActivityStack stack =
                         mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
+                if (stack == null) {
+                    Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
+                    return;
+                }
                 if (toTop) {
                     mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
                             null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
@@ -10530,14 +10558,14 @@
      */
     @Override
     public void dismissPip(boolean animate, int animationDuration) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 final PinnedActivityStack stack =
                         mStackSupervisor.getDefaultDisplay().getPinnedStack();
-
                 if (stack == null) {
+                    Slog.w(TAG, "dismissPip: pinned stack not found.");
                     return;
                 }
                 if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -10567,7 +10595,8 @@
      */
     @Override
     public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTopActivityToPinnedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "moveTopActivityToPinnedStack()");
         synchronized (this) {
             if (!mSupportsPictureInPicture) {
                 throw new IllegalStateException("moveTopActivityToPinnedStack:"
@@ -10586,13 +10615,14 @@
     @Override
     public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
             boolean preserveWindows, boolean animate, int animationDuration) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 if (animate) {
                     final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
                     if (stack == null) {
+                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
                         return;
                     }
                     if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -10621,8 +10651,7 @@
     public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds,
             Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
-        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                "resizeDockedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10637,8 +10666,7 @@
 
     @Override
     public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
-        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                "resizePinnedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10697,7 +10725,7 @@
 
     @Override
     public List<StackInfo> getAllStackInfos() {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10710,7 +10738,7 @@
 
     @Override
     public StackInfo getStackInfo(int windowingMode, int activityType) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -11871,8 +11899,7 @@
 
     @Override
     public void appNotRespondingViaProvider(IBinder connection) {
-        enforceCallingPermission(
-                android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+        enforceCallingPermission(REMOVE_TASKS, "appNotRespondingViaProvider()");
 
         final ContentProviderConnection conn = (ContentProviderConnection) connection;
         if (conn == null) {
@@ -18135,8 +18162,7 @@
     // =========================================================
 
     @Override
-    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
-            int flags) {
+    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) {
         enforceNotIsolatedCaller("getServices");
 
         final int callingUid = Binder.getCallingUid();
@@ -20080,7 +20106,7 @@
 
     @Override
     public StackInfo getFocusedStackInfo() throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -20120,7 +20146,8 @@
     @Override
     // TODO: API should just be about changing windowing modes...
     public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "moveTasksToFullscreenStack()");
         synchronized (this) {
             final long origId = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4cf2794..f942265 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2305,7 +2305,7 @@
     int runWrite(PrintWriter pw) {
         mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
-        mInternal.mRecentTasks.flush();
+        mInternal.getRecentTasks().flush();
         pw.println("All tasks persisted.");
         return 0;
     }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 0e8fc2c..2f0b649 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -204,7 +204,6 @@
     private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
 
     private static final boolean SHOW_ACTIVITY_START_TIME = true;
-    private static final String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
 
     private static final String ATTR_ID = "id";
     private static final String TAG_INTENT = "intent";
@@ -1058,7 +1057,7 @@
                 // We only allow home activities to be resizeable if they explicitly requested it.
                 info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
             }
-        } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
+        } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
             activityType = ACTIVITY_TYPE_RECENTS;
         } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
@@ -1562,17 +1561,7 @@
             return false;
         }
 
-        boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind;
-
-        if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
-            // On devices that support leanback only (Android TV), Recents activity can only be
-            // visible if the home stack is the focused stack or we are in split-screen mode.
-            final ActivityDisplay display = getDisplay();
-            boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack();
-            isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
-        }
-
-        return isVisible;
+        return !behindFullscreenActivity || mLaunchTaskBehind;
     }
 
     void makeVisibleIfNeeded(ActivityRecord starting) {
@@ -2074,7 +2063,7 @@
             final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId),
                     iconFilename);
             final String iconFilePath = iconFile.getAbsolutePath();
-            service.mRecentTasks.saveImage(icon, iconFilePath);
+            service.getRecentTasks().saveImage(icon, iconFilePath);
             _taskDescription.setIconFilename(iconFilePath);
         }
         taskDescription = _taskDescription;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 63c3a54..6ec158e 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -285,7 +285,7 @@
     RecentTasks mRecentTasks;
 
     /** Helper class to abstract out logic for fetching the set of currently running tasks */
-    private RunningTasks mRunningTasks = new RunningTasks();
+    private RunningTasks mRunningTasks;
 
     final ActivityStackSupervisorHandler mHandler;
 
@@ -572,6 +572,7 @@
     public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
         mService = service;
         mHandler = new ActivityStackSupervisorHandler(looper);
+        mRunningTasks = createRunningTasks();
         mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
         mKeyguardController = new KeyguardController(service, this);
 
@@ -584,6 +585,11 @@
         mRecentTasks.registerCallback(this);
     }
 
+    @VisibleForTesting
+    RunningTasks createRunningTasks() {
+        return new RunningTasks();
+    }
+
     /**
      * At the time when the constructor runs, the power manager has not yet been
      * initialized.  So we initialize our wakelocks afterwards.
@@ -1559,7 +1565,10 @@
             return false;
         }
         if (options != null) {
-            if (options.getLaunchTaskId() != INVALID_STACK_ID) {
+            // If a launch task id is specified, then ensure that the caller is the recents
+            // component or has the START_TASKS_FROM_RECENTS permission
+            if (options.getLaunchTaskId() != INVALID_TASK_ID
+                    && !mRecentTasks.isCallerRecents(callingUid)) {
                 final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
                         callingPid, callingUid);
                 if (startInTaskPerm == PERMISSION_DENIED) {
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index 66e975f..17626ea 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -82,7 +82,7 @@
                 if (tr == null) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
-                return mService.mRecentTasks.createRecentTaskInfo(tr);
+                return mService.getRecentTasks().createRecentTaskInfo(tr);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index ea6e919..0b9e0a2 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -37,8 +37,6 @@
 import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
-import com.google.android.collect.Sets;
-
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.ComponentName;
@@ -58,9 +56,8 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.MutableBoolean;
-import android.util.MutableInt;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -68,6 +65,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.am.TaskRecord.TaskActivitiesReport;
 
+import com.google.android.collect.Sets;
+
 import java.io.File;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -125,6 +124,13 @@
     private final UserController mUserController;
 
     /**
+     * Keeps track of the static recents package/component which is granted additional permissions
+     * to call recents-related APIs.
+     */
+    private int mRecentsUid = -1;
+    private ComponentName mRecentsComponent = null;
+
+    /**
      * Mapping of user id -> whether recent tasks have been loaded for that user.
      */
     private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
@@ -153,8 +159,7 @@
     private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
     private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
     private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray();
-    private final TaskRecord.TaskActivitiesReport mTmpReport =
-            new TaskRecord.TaskActivitiesReport();
+    private final TaskActivitiesReport mTmpReport = new TaskActivitiesReport();
 
     @VisibleForTesting
     RecentTasks(ActivityManagerService service, TaskPersister taskPersister,
@@ -174,7 +179,7 @@
         mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this);
         mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
         mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
-        loadParametersFromResources(service.mContext.getResources());
+        loadParametersFromResources(res);
     }
 
     @VisibleForTesting
@@ -218,6 +223,47 @@
                 : -1;
     }
 
+    /**
+     * Loads the static recents component.  This is called after the system is ready, but before
+     * any dependent services (like SystemUI) is started.
+     */
+    void loadRecentsComponent(Resources res) {
+        final String rawRecentsComponent = res.getString(
+                com.android.internal.R.string.config_recentsComponentName);
+        if (TextUtils.isEmpty(rawRecentsComponent)) {
+            return;
+        }
+
+        final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
+        if (cn != null) {
+            try {
+                final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+                        .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+                if (appInfo != null) {
+                    mRecentsUid = appInfo.uid;
+                    mRecentsComponent = cn;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not load application info for recents component: " + cn);
+            }
+        }
+    }
+
+    /**
+     * @return whether the current caller has the same uid as the recents component.
+     */
+    boolean isCallerRecents(int callingUid) {
+        return UserHandle.isSameApp(callingUid, mRecentsUid);
+    }
+
+    /**
+     * @return whether the given component is the recents component and shares the same uid as the
+     *         recents component.
+     */
+    boolean isRecentsComponent(ComponentName cn, int uid) {
+        return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
+    }
+
     void registerCallback(Callbacks callback) {
         mCallbacks.add(callback);
     }
@@ -340,6 +386,7 @@
     }
 
     void onSystemReadyLocked() {
+        loadRecentsComponent(mService.mContext.getResources());
         mTasks.clear();
         mTaskPersister.startPersisting();
     }
@@ -1328,12 +1375,14 @@
 
     void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) {
         pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
+        pw.println("mRecentsUid=" + mRecentsUid);
+        pw.println("mRecentsComponent=" + mRecentsComponent);
         if (mTasks.isEmpty()) {
             return;
         }
 
-        final MutableBoolean printedAnything = new MutableBoolean(false);
-        final MutableBoolean printedHeader = new MutableBoolean(false);
+        boolean printedAnything = false;
+        boolean printedHeader = false;
         final int size = mTasks.size();
         for (int i = 0; i < size; i++) {
             final TaskRecord tr = mTasks.get(i);
@@ -1342,10 +1391,10 @@
                 continue;
             }
 
-            if (!printedHeader.value) {
+            if (!printedHeader) {
                 pw.println("  Recent tasks:");
-                printedHeader.value = true;
-                printedAnything.value = true;
+                printedHeader = true;
+                printedAnything = true;
             }
             pw.print("  * Recent #"); pw.print(i); pw.print(": ");
             pw.println(tr);
@@ -1354,7 +1403,7 @@
             }
         }
 
-        if (!printedAnything.value) {
+        if (!printedAnything) {
             pw.println("  (nothing)");
         }
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a0c5cfa..1b42818 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2020,7 +2020,7 @@
 
         void loadUserRecents(int userId) {
             synchronized (mService) {
-                mService.mRecentTasks.loadUserRecentsLocked(userId);
+                mService.getRecentTasks().loadUserRecentsLocked(userId);
             }
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 3d5d87c..198cc6d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -78,7 +78,11 @@
     }
 
     protected ActivityManagerService createActivityManagerService() {
-        final ActivityManagerService service = spy(new TestActivityManagerService(mContext));
+        return setupActivityManagerService(new TestActivityManagerService(mContext));
+    }
+
+    protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
+        service = spy(service);
         service.mWindowManager = prepareMockWindowManager();
         return service;
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index 939e989..3c9b542 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -16,44 +16,55 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
 
+import static java.lang.Integer.MAX_VALUE;
+
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
-import android.os.Debug;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.MutableLong;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.server.am.RecentTasks.Callbacks;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -67,13 +78,17 @@
     private static final int TEST_QUIET_USER_ID = 20;
     private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
     private static final UserInfo QUIET_USER_INFO = new UserInfo();
+    private static final ComponentName MY_COMPONENT = new ComponentName(
+            RecentTasksTest.class.getPackage().getName(), RecentTasksTest.class.getName());
     private static int LAST_TASK_ID = 1;
+    private static int INVALID_STACK_ID = 999;
 
     private Context mContext = InstrumentationRegistry.getContext();
     private ActivityManagerService mService;
     private ActivityStack mStack;
     private TestTaskPersister mTaskPersister;
     private RecentTasks mRecentTasks;
+    private RunningTasks mRunningTasks;
 
     private static ArrayList<TaskRecord> mTasks = new ArrayList<>();
     private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>();
@@ -91,6 +106,14 @@
         }
 
         @Override
+        Set<Integer> getProfileIds(int userId) {
+            Set<Integer> profileIds = new HashSet<>();
+            profileIds.add(TEST_USER_0_ID);
+            profileIds.add(TEST_QUIET_USER_ID);
+            return profileIds;
+        }
+
+        @Override
         UserInfo getUserInfo(int userId) {
             switch (userId) {
                 case TEST_USER_0_ID:
@@ -108,12 +131,12 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        mService = createActivityManagerService();
+        mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
+        mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
+        mRecentTasks = mService.getRecentTasks();
+        mRecentTasks.loadParametersFromResources(mContext.getResources());
         mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
-        mRecentTasks = new RecentTasks(mService, mTaskPersister, new TestUserController(mService));
-        mRecentTasks.loadParametersFromResources(mContext.getResources());
         mCallbacksRecorder = new CallbacksRecorder();
         mRecentTasks.registerCallback(mCallbacksRecorder);
         QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
@@ -322,6 +345,103 @@
         assertTrimmed(mTasks.get(0), mTasks.get(1));
     }
 
+    @Test
+    public void testNotRecentsComponent_denyApiAccess() throws Exception {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+                anyInt(), anyInt());
+
+        // Expect the following methods to fail due to recents component not being set
+        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(
+                TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
+        testRecentTasksApis(false /* expectNoSecurityException */);
+        // Don't throw for the following tests
+        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.DENY);
+        testGetTasksApis(false /* expectNoSecurityException */);
+    }
+
+    @Test
+    public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+                anyInt(), anyInt());
+
+        // Set the recents component and ensure that the following calls do not fail
+        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.GRANT);
+        testRecentTasksApis(true /* expectNoSecurityException */);
+        testGetTasksApis(true /* expectNoSecurityException */);
+    }
+
+    private void testRecentTasksApis(boolean expectCallable) {
+        assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
+        assertSecurityException(expectCallable,
+                () -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED}));
+        assertSecurityException(expectCallable,
+                () -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED}));
+        assertSecurityException(expectCallable, () -> mService.removeTask(0));
+        assertSecurityException(expectCallable,
+                () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTaskToDockedStack(0, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, true,
+                        true, new Rect()));
+        assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
+        assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+        assertSecurityException(expectCallable,
+                () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+        assertSecurityException(expectCallable,
+                () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
+                        new Rect()));
+        assertSecurityException(expectCallable,
+                () -> mService.resizePinnedStack(new Rect(), new Rect()));
+        assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
+        assertSecurityException(expectCallable,
+                () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.getFocusedStackInfo();
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable,
+                () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
+        assertSecurityException(expectCallable,
+                () -> mService.startActivityFromRecents(0, new Bundle()));
+        assertSecurityException(expectCallable,
+                () -> mService.getTaskSnapshot(0, true));
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.registerTaskStackListener(null);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.unregisterTaskStackListener(null);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
+        assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
+        assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0));
+    }
+
+    private void testGetTasksApis(boolean expectCallable) {
+        mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
+        mService.getTasks(MAX_VALUE);
+        if (expectCallable) {
+            assertTrue(((TestRecentTasks) mRecentTasks).mLastAllowed);
+            assertTrue(((TestRunningTasks) mRunningTasks).mLastAllowed);
+        } else {
+            assertFalse(((TestRecentTasks) mRecentTasks).mLastAllowed);
+            assertFalse(((TestRunningTasks) mRunningTasks).mLastAllowed);
+        }
+    }
+
     private ComponentName createComponent(String className) {
         return new ComponentName(mContext.getPackageName(), className);
     }
@@ -367,6 +487,55 @@
         }
     }
 
+    private void assertSecurityException(boolean expectCallable, Runnable runnable) {
+        boolean noSecurityException = true;
+        try {
+            runnable.run();
+        } catch (SecurityException se) {
+            noSecurityException = false;
+        } catch (Exception e) {
+            // We only care about SecurityExceptions, fall through here
+            e.printStackTrace();
+        }
+        if (noSecurityException != expectCallable) {
+            fail("Expected callable: " + expectCallable + " but got no security exception: "
+                    + noSecurityException);
+        }
+    }
+
+    private class MyTestActivityManagerService extends TestActivityManagerService {
+        MyTestActivityManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected ActivityStackSupervisor createStackSupervisor() {
+            return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper());
+        }
+
+        @Override
+        protected RecentTasks createRecentTasks() {
+            return new TestRecentTasks(this, mTaskPersister, new TestUserController(this));
+        }
+
+        @Override
+        public boolean isUserRunning(int userId, int flags) {
+            return true;
+        }
+    }
+
+    private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
+        public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+            super(service, looper);
+        }
+
+        @Override
+        RunningTasks createRunningTasks() {
+            mRunningTasks = new TestRunningTasks();
+            return mRunningTasks;
+        }
+    }
+
     private static class CallbacksRecorder implements Callbacks {
         ArrayList<TaskRecord> added = new ArrayList<>();
         ArrayList<TaskRecord> trimmed = new ArrayList<>();
@@ -417,4 +586,61 @@
             return super.restoreTasksForUserLocked(userId, preaddedTasks);
         }
     }
+
+    private static class TestRecentTasks extends RecentTasks {
+        static final int GRANT = 0;
+        static final int DENY = 1;
+        static final int DENY_THROW_SECURITY_EXCEPTION = 2;
+
+        private boolean mOverrideIsCallerRecents;
+        private int mIsCallerRecentsPolicy;
+        boolean mLastAllowed;
+
+        TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+                UserController userController) {
+            super(service, taskPersister, userController);
+        }
+
+        @Override
+        boolean isCallerRecents(int callingUid) {
+            if (mOverrideIsCallerRecents) {
+                switch (mIsCallerRecentsPolicy) {
+                    case GRANT:
+                        return true;
+                    case DENY:
+                        return false;
+                    case DENY_THROW_SECURITY_EXCEPTION:
+                        throw new SecurityException();
+                }
+            }
+            return super.isCallerRecents(callingUid);
+        }
+
+        void setIsCallerRecentsOverride(int policy) {
+            mOverrideIsCallerRecents = true;
+            mIsCallerRecentsPolicy = policy;
+        }
+
+        @Override
+        ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+                boolean getTasksAllowed,
+                boolean getDetailedTasks, int userId, int callingUid) {
+            mLastAllowed = getTasksAllowed;
+            return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
+                    callingUid);
+        }
+    }
+
+    private static class TestRunningTasks extends RunningTasks {
+        boolean mLastAllowed;
+
+        @Override
+        void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
+                int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
+                int callingUid, boolean allowed) {
+            mLastAllowed = allowed;
+            super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
+                    callingUid, allowed);
+        }
+    }
 }
\ No newline at end of file