Add support to preload recents activity in background

The recents activity may be died in background for some reasons.
Then there may have jank when swiping-up the recents because it
needs to initialize at the same time. Especially when the recents
is the home activity. The case of configuration change is similar.

This CL provides the ability to start the recents activity in
background without affecting the foreground applications.

Bug: 127350205
Test: atest RecentsAnimationTest#testPreloadRecentsActivity
Change-Id: Idef72ce986eb910b302953705365437bc5ec2bf8
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0faea61..bc67cb0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3162,7 +3162,7 @@
 
     boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
         return ensureActivityConfiguration(globalChanges, preserveWindow,
-                false /* ignoreStopState */);
+                false /* ignoreVisibility */);
     }
 
     /**
@@ -3172,15 +3172,15 @@
      * @param globalChanges The changes to the global configuration.
      * @param preserveWindow If the activity window should be preserved on screen if the activity
      *                       is relaunched.
-     * @param ignoreStopState If we should try to relaunch the activity even if it is in the stopped
-     *                        state. This is useful for the case where we know the activity will be
-     *                        visible soon and we want to ensure its configuration before we make it
-     *                        visible.
+     * @param ignoreVisibility If we should try to relaunch the activity even if it is invisible
+     *                         (stopped state). This is useful for the case where we know the
+     *                         activity will be visible soon and we want to ensure its configuration
+     *                         before we make it visible.
      * @return False if the activity was relaunched and true if it wasn't relaunched because we
      *         can't or the app handles the specific configuration that is changing.
      */
     boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
-            boolean ignoreStopState) {
+            boolean ignoreVisibility) {
         final ActivityStack stack = getActivityStack();
         if (stack.mConfigWillChange) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
@@ -3196,15 +3196,9 @@
             return true;
         }
 
-        if (!ignoreStopState && (mState == STOPPING || mState == STOPPED)) {
+        if (!ignoreVisibility && (mState == STOPPING || mState == STOPPED || !shouldBeVisible())) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check stopped or stopping: " + this);
-            return true;
-        }
-
-        if (!shouldBeVisible()) {
-            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Skipping config check invisible stack: " + this);
+                    "Skipping config check invisible: " + this);
             return true;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 74c3069..f894188 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1881,8 +1881,7 @@
         mRootActivityContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
     }
 
-    private void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed,
-            String reason) {
+    void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed, String reason) {
         if (!mStackSupervisor.mStoppingActivities.contains(r)) {
             EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, r.mUserId,
                     System.identityHashCode(r), r.shortComponentName, reason);
@@ -2177,7 +2176,7 @@
                         // sure it matches the current configuration.
                         if (r != starting && notifyClients) {
                             r.ensureActivityConfiguration(0 /* globalChanges */, preserveWindows,
-                                    true /* ignoreStopState */);
+                                    true /* ignoreVisibility */);
                         }
 
                         if (!r.attachedToProcess()) {
@@ -3139,8 +3138,10 @@
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.getTaskRecord();
         final int taskId = rTask.taskId;
+        final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
         // mLaunchTaskBehind tasks get placed at the back of the task stack.
-        if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
+        if (!r.mLaunchTaskBehind && allowMoveToFront
+                && (taskForIdLocked(taskId) == null || newTask)) {
             // Last activity in task had been removed or ActivityManagerService is reusing task.
             // Insert or replace.
             // Might not even be in.
@@ -3199,7 +3200,9 @@
 
         task.setFrontOfTask();
 
-        if (!isHomeOrRecentsStack() || numActivities() > 0) {
+        // The transition animation and starting window are not needed if {@code allowMoveToFront}
+        // is false, because the activity won't be visible.
+        if ((!isHomeOrRecentsStack() || numActivities() > 0) && allowMoveToFront) {
             final DisplayContent dc = getDisplay().mDisplayContent;
             if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                     "Prepare open transition: starting " + r);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0a88eef..eda9d24 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2307,12 +2307,12 @@
         // isLockTaskModeViolation fails below.
 
         if (mReuseTask == null) {
+            final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
             final TaskRecord task = mTargetStack.createTaskRecord(
                     mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId),
                     mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
-                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
-                    mOptions);
+                    mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
             addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
             updateBounds(mStartActivity.getTaskRecord(), mLaunchParams.mBounds);
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9fc278e..c5a0de1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1451,9 +1451,15 @@
                 .execute();
     }
 
+    /**
+     * Start the recents activity to perform the recents animation.
+     *
+     * @param intent The intent to start the recents activity.
+     * @param recentsAnimationRunner Pass {@code null} to only preload the activity.
+     */
     @Override
-    public void startRecentsActivity(Intent intent, IAssistDataReceiver assistDataReceiver,
-            IRecentsAnimationRunner recentsAnimationRunner) {
+    public void startRecentsActivity(Intent intent, @Deprecated IAssistDataReceiver unused,
+            @Nullable IRecentsAnimationRunner recentsAnimationRunner) {
         enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "startRecentsActivity()");
         final int callingPid = Binder.getCallingPid();
         final long origId = Binder.clearCallingIdentity();
@@ -1464,9 +1470,13 @@
 
                 // Start a new recents animation
                 final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor,
-                        getActivityStartController(), mWindowManager, callingPid);
-                anim.startRecentsActivity(intent, recentsAnimationRunner, recentsComponent,
-                        recentsUid, assistDataReceiver);
+                        getActivityStartController(), mWindowManager, intent, recentsComponent,
+                        recentsUid, callingPid);
+                if (recentsAnimationRunner == null) {
+                    anim.preloadRecentsActivity();
+                } else {
+                    anim.startRecentsActivity(recentsAnimationRunner);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 434239f..bf627ec 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -34,7 +34,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
 
 import android.app.ActivityOptions;
-import android.app.IAssistDataReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.RemoteException;
@@ -58,7 +57,12 @@
     private final ActivityStartController mActivityStartController;
     private final WindowManagerService mWindowManager;
     private final ActivityDisplay mDefaultDisplay;
+    private final Intent mTargetIntent;
+    private final ComponentName mRecentsComponent;
+    private final int mRecentsUid;
     private final int mCallingPid;
+    private final int mUserId;
+    private final int mTargetActivityType;
 
     /**
      * The activity which has been launched behind. We need to remember the activity because the
@@ -66,27 +70,90 @@
      * for the exact activity.
      */
     private ActivityRecord mLaunchedTargetActivity;
-    private int mTargetActivityType;
 
     // The stack to restore the target stack behind when the animation is finished
     private ActivityStack mRestoreTargetBehindStack;
 
     RecentsAnimation(ActivityTaskManagerService atm, ActivityStackSupervisor stackSupervisor,
             ActivityStartController activityStartController, WindowManagerService wm,
-            int callingPid) {
+            Intent targetIntent, ComponentName recentsComponent, int recentsUid, int callingPid) {
         mService = atm;
         mStackSupervisor = stackSupervisor;
         mDefaultDisplay = mService.mRootActivityContainer.getDefaultDisplay();
         mActivityStartController = activityStartController;
         mWindowManager = wm;
+        mTargetIntent = targetIntent;
+        mRecentsComponent = recentsComponent;
+        mRecentsUid = recentsUid;
         mCallingPid = callingPid;
+        mUserId = atm.getCurrentUserId();
+        mTargetActivityType = targetIntent.getComponent() != null
+                && recentsComponent.equals(targetIntent.getComponent())
+                        ? ACTIVITY_TYPE_RECENTS
+                        : ACTIVITY_TYPE_HOME;
     }
 
-    void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner,
-            ComponentName recentsComponent, int recentsUid,
-            @Deprecated IAssistDataReceiver assistDataReceiver) {
-        if (DEBUG) Slog.d(TAG, "startRecentsActivity(): intent=" + intent
-                + " assistDataReceiver=" + assistDataReceiver);
+    /**
+     * Starts the recents activity in background without animation if the record doesn't exist or
+     * the client isn't launched. If the recents activity is already alive, ensure its configuration
+     * is updated to the current one.
+     */
+    void preloadRecentsActivity() {
+        if (DEBUG) Slog.d(TAG, "Preload recents with " + mTargetIntent);
+        ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
+                mTargetActivityType);
+        ActivityRecord targetActivity = getTargetActivity(targetStack);
+        if (targetActivity != null) {
+            if (targetActivity.visible || targetActivity.isTopRunningActivity()) {
+                // The activity is ready.
+                return;
+            }
+            if (targetActivity.attachedToProcess()) {
+                // The activity may be relaunched if it cannot handle the current configuration
+                // changes. The activity will be paused state if it is relaunched, otherwise it
+                // keeps the original stopped state.
+                targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
+                        false /* preserveWindow */, true /* ignoreVisibility */);
+                if (DEBUG) Slog.d(TAG, "Updated config=" + targetActivity.getConfiguration());
+            }
+        } else {
+            // Create the activity record. Because the activity is invisible, this doesn't really
+            // start the client.
+            startRecentsActivityInBackground("preloadRecents");
+            targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType);
+            targetActivity = getTargetActivity(targetStack);
+            if (targetActivity == null) {
+                Slog.w(TAG, "Cannot start " + mTargetIntent);
+                return;
+            }
+        }
+
+        if (!targetActivity.attachedToProcess()) {
+            if (DEBUG) Slog.d(TAG, "Real start recents");
+            mStackSupervisor.startSpecificActivityLocked(targetActivity, false /* andResume */,
+                    false /* checkConfig */);
+            // Make sure the activity won't be involved in transition.
+            if (targetActivity.mAppWindowToken != null) {
+                targetActivity.mAppWindowToken.getDisplayContent().mUnknownAppVisibilityController
+                        .appRemovedOrHidden(targetActivity.mAppWindowToken);
+            }
+        }
+
+        // Invisible activity should be stopped. If the recents activity is alive and its doesn't
+        // need to relaunch by current configuration, then it may be already in stopped state.
+        if (!targetActivity.isState(ActivityStack.ActivityState.STOPPING,
+                ActivityStack.ActivityState.STOPPED)) {
+            // Add to stopping instead of stop immediately. So the client has the chance to perform
+            // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
+            // things (e.g. the measure can be done earlier). The actual stop will be performed when
+            // it reports idle.
+            targetStack.addToStopping(targetActivity, true /* scheduleIdle */,
+                    true /* idleDelayed */, "preloadRecents");
+        }
+    }
+
+    void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
+        if (DEBUG) Slog.d(TAG, "startRecentsActivity(): intent=" + mTargetIntent);
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
 
         // TODO(multi-display) currently only support recents animation in default display.
@@ -100,15 +167,9 @@
         }
 
         // If the activity is associated with the recents stack, then try and get that first
-        final int userId = mService.getCurrentUserId();
-        mTargetActivityType = intent.getComponent() != null
-                && recentsComponent.equals(intent.getComponent())
-                        ? ACTIVITY_TYPE_RECENTS
-                        : ACTIVITY_TYPE_HOME;
         ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
                 mTargetActivityType);
-        ActivityRecord targetActivity = getTargetActivity(targetStack, intent.getComponent(),
-                userId);
+        ActivityRecord targetActivity = getTargetActivity(targetStack);
         final boolean hasExistingActivity = targetActivity != null;
         if (hasExistingActivity) {
             final ActivityDisplay display = targetActivity.getDisplay();
@@ -127,7 +188,7 @@
                     true /* forceSend */, targetActivity);
         }
 
-        mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+        mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);
 
         mService.mH.post(() -> mService.mAmInternal.setRunningRemoteAnimation(mCallingPid, true));
 
@@ -148,23 +209,12 @@
                 }
             } else {
                 // No recents activity, create the new recents activity bottom most
-                ActivityOptions options = ActivityOptions.makeBasic();
-                options.setLaunchActivityType(mTargetActivityType);
-                options.setAvoidMoveToFront();
-                intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
-
-                mActivityStartController
-                        .obtainStarter(intent, "startRecentsActivity_noTargetActivity")
-                        .setCallingUid(recentsUid)
-                        .setCallingPackage(recentsComponent.getPackageName())
-                        .setActivityOptions(SafeActivityOptions.fromBundle(options.toBundle()))
-                        .setMayWait(userId)
-                        .execute();
+                startRecentsActivityInBackground("startRecentsActivity_noTargetActivity");
 
                 // Move the recents activity into place for the animation
                 targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
                         mTargetActivityType);
-                targetActivity = getTargetActivity(targetStack, intent.getComponent(), userId);
+                targetActivity = getTargetActivity(targetStack);
                 mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
                 if (DEBUG) {
                     Slog.d(TAG, "Moved stack=" + targetStack + " behind stack="
@@ -176,7 +226,7 @@
 
                 // TODO: Maybe wait for app to draw in this particular case?
 
-                if (DEBUG) Slog.d(TAG, "Started intent=" + intent);
+                if (DEBUG) Slog.d(TAG, "Started intent=" + mTargetIntent);
             }
 
             // Mark the target activity as launch-behind to bump its visibility for the
@@ -383,6 +433,21 @@
         }
     }
 
+    private void startRecentsActivityInBackground(String reason) {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchActivityType(mTargetActivityType);
+        options.setAvoidMoveToFront();
+        mTargetIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
+
+        mActivityStartController
+                .obtainStarter(mTargetIntent, reason)
+                .setCallingUid(mRecentsUid)
+                .setCallingPackage(mRecentsComponent.getPackageName())
+                .setActivityOptions(new SafeActivityOptions(options))
+                .setMayWait(mUserId)
+                .execute();
+    }
+
     /**
      * Called only when the animation should be canceled prior to starting.
      */
@@ -412,15 +477,15 @@
      * @return the top activity in the {@param targetStack} matching the {@param component}, or just
      * the top activity of the top task if no task matches the component.
      */
-    private ActivityRecord getTargetActivity(ActivityStack targetStack, ComponentName component,
-            int userId) {
+    private ActivityRecord getTargetActivity(ActivityStack targetStack) {
         if (targetStack == null) {
             return null;
         }
 
         for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
             final TaskRecord task = targetStack.getChildAt(i);
-            if (task.userId == userId && task.getBaseIntent().getComponent().equals(component)) {
+            if (task.userId == mUserId
+                    && task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent())) {
                 return task.getTopActivity();
             }
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 4986a6d..ecf3acd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -85,6 +85,7 @@
 
 import java.io.File;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * A base class to handle common operations in activity related unit tests.
@@ -169,8 +170,12 @@
      * Delegates task creation to {@link #TaskBuilder} to avoid the dependency of window hierarchy
      * when starting activity in unit tests.
      */
-    void mockTaskRecordFactory() {
-        final TaskRecord task = new TaskBuilder(mSupervisor).setCreateStack(false).build();
+    void mockTaskRecordFactory(Consumer<TaskBuilder> taskBuilderSetup) {
+        final TaskBuilder taskBuilder = new TaskBuilder(mSupervisor).setCreateStack(false);
+        if (taskBuilderSetup != null) {
+            taskBuilderSetup.accept(taskBuilder);
+        }
+        final TaskRecord task = taskBuilder.build();
         final TaskRecordFactory factory = mock(TaskRecordFactory.class);
         TaskRecord.setTaskRecordFactory(factory);
         doReturn(task).when(factory).create(any() /* service */, anyInt() /* taskId */,
@@ -178,6 +183,10 @@
                 any() /* voiceInteractor */);
     }
 
+    void mockTaskRecordFactory() {
+        mockTaskRecordFactory(null /* taskBuilderSetup */);
+    }
+
     /**
      * Builder for creating new activities.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 1f8b33e..9630b7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -42,8 +42,11 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 
+import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRecentsAnimationRunner;
 
@@ -111,6 +114,57 @@
     }
 
     @Test
+    public void testPreloadRecentsActivity() {
+        // Ensure that the fake recent component can be resolved by the recents intent.
+        mockTaskRecordFactory(builder -> builder.setComponent(mRecentsComponent));
+        ActivityInfo aInfo = new ActivityInfo();
+        aInfo.applicationInfo = new ApplicationInfo();
+        aInfo.applicationInfo.uid = 10001;
+        aInfo.applicationInfo.targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+        aInfo.packageName = aInfo.applicationInfo.packageName = mRecentsComponent.getPackageName();
+        aInfo.processName = "recents";
+        doReturn(aInfo).when(mSupervisor).resolveActivity(any() /* intent */, any() /* rInfo */,
+                anyInt() /* startFlags */, any() /* profilerInfo */);
+
+        // Assume its process is alive because the caller should be the recents service.
+        WindowProcessController wpc = new WindowProcessController(mService, aInfo.applicationInfo,
+                aInfo.processName, aInfo.applicationInfo.uid, 0 /* userId */,
+                mock(Object.class) /* owner */, mock(WindowProcessListener.class));
+        wpc.setThread(mock(IApplicationThread.class));
+        doReturn(wpc).when(mService).getProcessController(eq(wpc.mName), eq(wpc.mUid));
+
+        Intent recentsIntent = new Intent().setComponent(mRecentsComponent);
+        // Null animation indicates to preload.
+        mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+                null /* recentsAnimationRunner */);
+
+        ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        ActivityStack recentsStack = display.getStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_RECENTS);
+        assertThat(recentsStack).isNotNull();
+
+        ActivityRecord recentsActivity = recentsStack.getTopActivity();
+        // The activity is started in background so it should be invisible and will be stopped.
+        assertThat(recentsActivity).isNotNull();
+        assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
+        assertFalse(recentsActivity.visible);
+
+        // Assume it is stopped to test next use case.
+        recentsActivity.activityStoppedLocked(null /* newIcicle */, null /* newPersistentState */,
+                null /* description */);
+        mSupervisor.mStoppingActivities.remove(recentsActivity);
+
+        spyOn(recentsActivity);
+        // Start when the recents activity exists. It should ensure the configuration.
+        mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+                null /* recentsAnimationRunner */);
+
+        verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
+                anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */);
+        assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
+    }
+
+    @Test
     public void testRestartRecentsActivity() throws Exception {
         // Have a recents activity that is not attached to its process (ActivityRecord.app = null).
         ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();