Revert "Revert "2/ Add support for remote Recents animation""

This reverts commit 9f8518e532e41ba57916afc49bba72bc23ad3eda.

Reason for revert: Testing relanding changes with ag/3515280

Change-Id: I410bd752c815a5b998a719453def01e00a9d47c8
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 46ab0e0..aa8d56b 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -664,6 +665,64 @@
                 && (mSupervisor.mService.mRunningVoice == null);
     }
 
+    /**
+     * @return the stack currently above the home stack.  Can be null if there is no home stack, or
+     *         the home stack is already on top.
+     */
+    ActivityStack getStackAboveHome() {
+        if (mHomeStack == null) {
+            // Skip if there is no home stack
+            return null;
+        }
+
+        final int stackIndex = mStacks.indexOf(mHomeStack) + 1;
+        return (stackIndex < mStacks.size()) ? mStacks.get(stackIndex) : null;
+    }
+
+    /**
+     * Adjusts the home stack behind the last visible stack in the display if necessary. Generally
+     * used in conjunction with {@link #moveHomeStackBehindStack}.
+     */
+    void moveHomeStackBehindBottomMostVisibleStack() {
+        if (mHomeStack == null) {
+            // Skip if there is no home stack
+            return;
+        }
+
+        // Move the home stack to the bottom to not affect the following visibility checks
+        positionChildAtBottom(mHomeStack);
+
+        // Find the next position where the homes stack should be placed
+        final int numStacks = mStacks.size();
+        for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            if (stack == mHomeStack) {
+                continue;
+            }
+            final int winMode = stack.getWindowingMode();
+            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN ||
+                    winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+            if (stack.shouldBeVisible(null) && isValidWindowingMode) {
+                // Move the home stack to behind this stack
+                positionChildAt(mHomeStack, Math.max(0, stackNdx - 1));
+                break;
+            }
+        }
+    }
+
+    /**
+     * Moves the home stack behind the given {@param stack} if possible. If {@param stack} is not
+     * currently in the display, then then the home stack is moved to the back. Generally used in
+     * conjunction with {@link #moveHomeStackBehindBottomMostVisibleStack}.
+     */
+    void moveHomeStackBehindStack(ActivityStack behindStack) {
+        if (behindStack == null) {
+            return;
+        }
+
+        positionChildAt(mHomeStack, Math.max(0, mStacks.indexOf(behindStack) - 1));
+    }
+
     boolean isSleeping() {
         return mSleeping;
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f469437..f4bf884 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -39,6 +39,7 @@
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -377,6 +378,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.ProtoUtils;
 import android.view.Gravity;
+import android.view.IRecentsAnimationRunner;
 import android.view.LayoutInflater;
 import android.view.RemoteAnimationDefinition;
 import android.view.View;
@@ -450,6 +452,7 @@
 import com.android.server.utils.PriorityDump;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.PinnedStackWindowController;
+import com.android.server.wm.RecentsAnimationController;
 import com.android.server.wm.WindowManagerService;
 
 import dalvik.system.VMRuntime;
@@ -5096,23 +5099,16 @@
     }
 
     @Override
-    public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle options,
-            Bundle activityOptions, int userId) {
-        if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
-            String msg = "Permission Denial: startRecentsActivity() from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " not recent tasks package";
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-
-        final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options);
-        final int recentsUid = mRecentTasks.getRecentsComponentUid();
-        final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
-        final String recentsPackage = recentsComponent.getPackageName();
+    public void startRecentsActivity(Intent intent, IAssistDataReceiver assistDataReceiver,
+                IRecentsAnimationRunner recentsAnimationRunner) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "startRecentsActivity()");
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
+                final int recentsUid = mRecentTasks.getRecentsComponentUid();
+                final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
+                final String recentsPackage = recentsComponent.getPackageName();
+
                 // If provided, kick off the request for the assist data in the background before
                 // starting the activity
                 if (assistDataReceiver != null) {
@@ -5129,17 +5125,24 @@
                             recentsUid, recentsPackage);
                 }
 
-                final Intent intent = new Intent();
-                intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
-                intent.setComponent(recentsComponent);
-                intent.putExtras(options);
+                // Start a new recents animation
+                final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor,
+                        mActivityStartController, mWindowManager, mUserController);
+                anim.startRecentsActivity(intent, recentsAnimationRunner, recentsComponent,
+                        recentsUid);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
 
-                return mActivityStartController.obtainStarter(intent, "startRecentsActivity")
-                        .setCallingUid(recentsUid)
-                        .setCallingPackage(recentsPackage)
-                        .setActivityOptions(safeOptions)
-                        .setMayWait(userId)
-                        .execute();
+    @Override
+    public void cancelRecentsAnimation() {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()");
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                mWindowManager.cancelRecentsAnimation();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index bfb563f..510a3fa 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -297,6 +297,7 @@
     private RunningTasks mRunningTasks;
 
     final ActivityStackSupervisorHandler mHandler;
+    final Looper mLooper;
 
     /** Short cut */
     WindowManagerService mWindowManager;
@@ -581,6 +582,7 @@
 
     public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
         mService = service;
+        mLooper = looper;
         mHandler = new ActivityStackSupervisorHandler(looper);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 4dc30dd..8fd754a 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -302,6 +302,7 @@
         SafeActivityOptions activityOptions;
         boolean ignoreTargetSecurity;
         boolean componentSpecified;
+        boolean avoidMoveToFront;
         ActivityRecord[] outActivity;
         TaskRecord inTask;
         String reason;
@@ -356,6 +357,7 @@
             userId = 0;
             waitResult = null;
             mayWait = false;
+            avoidMoveToFront = false;
         }
 
         /**
@@ -390,6 +392,7 @@
             userId = request.userId;
             waitResult = request.waitResult;
             mayWait = request.mayWait;
+            avoidMoveToFront = request.avoidMoveToFront;
         }
     }
 
@@ -1485,19 +1488,23 @@
             mDoResume = false;
         }
 
-        if (mOptions != null && mOptions.getLaunchTaskId() != -1
-                && mOptions.getTaskOverlay()) {
-            r.mTaskOverlay = true;
-            if (!mOptions.canTaskOverlayResume()) {
-                final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
-                final ActivityRecord top = task != null ? task.getTopActivity() : null;
-                if (top != null && top.state != RESUMED) {
+        if (mOptions != null) {
+            if (mOptions.getLaunchTaskId() != -1 && mOptions.getTaskOverlay()) {
+                r.mTaskOverlay = true;
+                if (!mOptions.canTaskOverlayResume()) {
+                    final TaskRecord task = mSupervisor.anyTaskForIdLocked(
+                            mOptions.getLaunchTaskId());
+                    final ActivityRecord top = task != null ? task.getTopActivity() : null;
+                    if (top != null && top.state != RESUMED) {
 
-                    // The caller specifies that we'd like to be avoided to be moved to the front,
-                    // so be it!
-                    mDoResume = false;
-                    mAvoidMoveToFront = true;
+                        // The caller specifies that we'd like to be avoided to be moved to the
+                        // front, so be it!
+                        mDoResume = false;
+                        mAvoidMoveToFront = true;
+                    }
                 }
+            } else if (mOptions.getAvoidMoveToFront()) {
+                mAvoidMoveToFront = true;
             }
         }
 
@@ -1838,7 +1845,7 @@
         // Need to update mTargetStack because if task was moved out of it, the original stack may
         // be destroyed.
         mTargetStack = intentActivity.getStack();
-        if (!mMovedToFront && mDoResume) {
+        if (!mAvoidMoveToFront && !mMovedToFront && mDoResume) {
             if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack
                     + " from " + intentActivity);
             mTargetStack.moveToFront("intentActivityFound");
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
new file mode 100644
index 0000000..fe576fda
--- /dev/null
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 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.am;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_NONE;
+import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.view.IRecentsAnimationRunner;
+import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
+import com.android.server.wm.WindowManagerService;
+
+/**
+ * Manages the recents animation, including the reordering of the stacks for the transition and
+ * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
+ */
+class RecentsAnimation implements RecentsAnimationCallbacks {
+    private static final String TAG = RecentsAnimation.class.getSimpleName();
+
+    private static final int RECENTS_ANIMATION_TIMEOUT = 10 * 1000;
+
+    private final ActivityManagerService mService;
+    private final ActivityStackSupervisor mStackSupervisor;
+    private final ActivityStartController mActivityStartController;
+    private final WindowManagerService mWindowManager;
+    private final UserController mUserController;
+    private final Handler mHandler;
+
+    private final Runnable mCancelAnimationRunnable;
+
+    // The stack to restore the home stack behind when the animation is finished
+    private ActivityStack mRestoreHomeBehindStack;
+
+    RecentsAnimation(ActivityManagerService am, ActivityStackSupervisor stackSupervisor,
+            ActivityStartController activityStartController, WindowManagerService wm,
+            UserController userController) {
+        mService = am;
+        mStackSupervisor = stackSupervisor;
+        mActivityStartController = activityStartController;
+        mHandler = new Handler(mStackSupervisor.mLooper);
+        mWindowManager = wm;
+        mUserController = userController;
+        mCancelAnimationRunnable = () -> {
+            // The caller has not finished the animation in a predefined amount of time, so
+            // force-cancel the animation
+            mWindowManager.cancelRecentsAnimation();
+        };
+    }
+
+    void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner,
+            ComponentName recentsComponent, int recentsUid) {
+
+        // Cancel the previous recents animation if necessary
+        mWindowManager.cancelRecentsAnimation();
+
+        final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null;
+        if (!hasExistingHomeActivity) {
+            // No home activity
+            final ActivityOptions opts = ActivityOptions.makeBasic();
+            opts.setLaunchActivityType(ACTIVITY_TYPE_HOME);
+            opts.setAvoidMoveToFront();
+            intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
+
+            mActivityStartController.obtainStarter(intent, "startRecentsActivity_noHomeActivity")
+                    .setCallingUid(recentsUid)
+                    .setCallingPackage(recentsComponent.getPackageName())
+                    .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle()))
+                    .setMayWait(mUserController.getCurrentUserId())
+                    .execute();
+            mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+
+            // TODO: Maybe wait for app to draw in this particular case?
+        }
+
+        final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
+        final ActivityDisplay display = homeActivity.getDisplay();
+
+        // Save the initial position of the home activity stack to be restored to after the
+        // animation completes
+        mRestoreHomeBehindStack = hasExistingHomeActivity
+                ? display.getStackAboveHome()
+                : null;
+
+        // Move the home activity into place for the animation
+        display.moveHomeStackBehindBottomMostVisibleStack();
+
+        // Mark the home activity as launch-behind to bump its visibility for the
+        // duration of the gesture that is driven by the recents component
+        homeActivity.mLaunchTaskBehind = true;
+
+        // Fetch all the surface controls and pass them to the client to get the animation
+        // started
+        mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, display.mDisplayId);
+
+        // If we updated the launch-behind state, update the visibility of the activities after we
+        // fetch the visible tasks to be controlled by the animation
+        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+
+        // Post a timeout for the animation
+        mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT);
+    }
+
+    @Override
+    public void onAnimationFinished(boolean moveHomeToTop) {
+        mHandler.removeCallbacks(mCancelAnimationRunnable);
+        synchronized (mService) {
+            if (mWindowManager.getRecentsAnimationController() == null) return;
+
+            mWindowManager.inSurfaceTransaction(() -> {
+                mWindowManager.cleanupRecentsAnimation();
+
+                // Move the home stack to the front
+                final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
+                if (homeActivity == null) {
+                    return;
+                }
+
+                // Restore the launched-behind state
+                homeActivity.mLaunchTaskBehind = false;
+
+                if (moveHomeToTop) {
+                    // Bring the home stack to the front
+                    final ActivityStack homeStack = homeActivity.getStack();
+                    homeStack.mNoAnimActivities.add(homeActivity);
+                    homeStack.moveToFront("RecentsAnimation.onAnimationFinished()");
+                } else {
+                    // Restore the home stack to its previous position
+                    final ActivityDisplay display = homeActivity.getDisplay();
+                    display.moveHomeStackBehindStack(mRestoreHomeBehindStack);
+                }
+
+                mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false);
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
+            });
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d7a58d9..3f49f0c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1508,6 +1508,10 @@
         return mTaskStackContainers.getTopStack();
     }
 
+    ArrayList<Task> getVisibleTasks() {
+        return mTaskStackContainers.getVisibleTasks();
+    }
+
     void onStackWindowingModeChanged(TaskStack stack) {
         mTaskStackContainers.onStackWindowingModeChanged(stack);
     }
@@ -3260,6 +3264,16 @@
             return mSplitScreenPrimaryStack;
         }
 
+        ArrayList<Task> getVisibleTasks() {
+            final ArrayList<Task> visibleTasks = new ArrayList<>();
+            forAllTasks(task -> {
+                if (task.isVisible()) {
+                    visibleTasks.add(task);
+                }
+            });
+            return visibleTasks;
+        }
+
         /**
          * Adds the stack to this container.
          * @see DisplayContent#createStack(int, boolean, StackWindowController)
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
new file mode 100644
index 0000000..c7ad17b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2017 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 static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
+import android.app.WindowConfiguration;
+import android.graphics.GraphicBuffer;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controls a single instance of the remote driven recents animation. In particular, this allows
+ * the calling SystemUI to animate the visible task windows as a part of the transition. The remote
+ * runner is provided an animation controller which allows it to take screenshots and to notify
+ * window manager when the animation is completed. In addition, window manager may also notify the
+ * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.)
+ */
+public class RecentsAnimationController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentsAnimationController" : TAG_WM;
+    private static final boolean DEBUG = false;
+
+    private final WindowManagerService mService;
+    private final IRecentsAnimationRunner mRunner;
+    private final RecentsAnimationCallbacks mCallbacks;
+    private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+
+    // The recents component app token that is shown behind the visibile tasks
+    private AppWindowToken mHomeAppToken;
+
+    // We start the RecentsAnimationController in a pending-start state since we need to wait for
+    // the wallpaper/activity to draw before we can give control to the handler to start animating
+    // the visible task surfaces
+    private boolean mPendingStart = true;
+
+    // Set when the animation has been canceled
+    private boolean mCanceled = false;
+
+    // Whether or not the input consumer is enabled. The input consumer must be both registered and
+    // enabled for it to start intercepting touch events.
+    private boolean mInputConsumerEnabled;
+
+    public interface RecentsAnimationCallbacks {
+        void onAnimationFinished(boolean moveHomeToTop);
+    }
+
+    private final IRecentsAnimationController mController =
+            new IRecentsAnimationController.Stub() {
+
+        @Override
+        public TaskSnapshot screenshotTask(int taskId) {
+            if (DEBUG) Log.d(TAG, "screenshotTask(" + taskId + "): mCanceled=" + mCanceled);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mService.getWindowManagerLock()) {
+                    if (mCanceled) {
+                        return null;
+                    }
+                    for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+                        final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
+                        final Task task = adapter.mTask;
+                        if (task.mTaskId == taskId) {
+                            // TODO: Save this screenshot as the task snapshot?
+                            final Rect taskFrame = new Rect();
+                            task.getBounds(taskFrame);
+                            final GraphicBuffer buffer = SurfaceControl.captureLayers(
+                                    task.getSurfaceControl().getHandle(), taskFrame, 1f);
+                            final AppWindowToken topChild = task.getTopChild();
+                            final WindowState mainWindow = topChild.findMainWindow();
+                            return new TaskSnapshot(buffer, topChild.getConfiguration().orientation,
+                                    mainWindow.mStableInsets,
+                                    ActivityManager.isLowRamDeviceStatic() /* reduced */,
+                                    1.0f /* scale */);
+                        }
+                    }
+                    return null;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void finish(boolean moveHomeToTop) {
+            if (DEBUG) Log.d(TAG, "finish(" + moveHomeToTop + "): mCanceled=" + mCanceled);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mService.getWindowManagerLock()) {
+                    if (mCanceled) {
+                        return;
+                    }
+                }
+
+                // Note, the callback will handle its own synchronization, do not lock on WM lock
+                // prior to calling the callback
+                mCallbacks.onAnimationFinished(moveHomeToTop);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setInputConsumerEnabled(boolean enabled) {
+            if (DEBUG) Log.d(TAG, "setInputConsumerEnabled(" + enabled + "): mCanceled="
+                    + mCanceled);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mService.getWindowManagerLock()) {
+                    if (mCanceled) {
+                        return;
+                    }
+
+                    mInputConsumerEnabled = enabled;
+                    mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+                    mService.scheduleAnimationLocked();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    };
+
+    /**
+     * Initializes a new RecentsAnimationController.
+     *
+     * @param remoteAnimationRunner The remote runner which should be notified when the animation is
+     *                              ready to start or has been canceled
+     * @param callbacks Callbacks to be made when the animation finishes
+     * @param restoreHomeBehindStackId The stack id to restore the home stack behind once the
+     *                                 animation is complete. Will be passed to the callback.
+     */
+    RecentsAnimationController(WindowManagerService service,
+            IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks,
+            int displayId) {
+        mService = service;
+        mRunner = remoteAnimationRunner;
+        mCallbacks = callbacks;
+
+        final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+        final ArrayList<Task> visibleTasks = dc.getVisibleTasks();
+        if (visibleTasks.isEmpty()) {
+            cancelAnimation();
+            return;
+        }
+
+        // Make leashes for each of the visible tasks and add it to the recents animation to be
+        // started
+        final int taskCount = visibleTasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            final Task task = visibleTasks.get(i);
+            final WindowConfiguration config = task.getWindowConfiguration();
+            if (config.tasksAreFloating()
+                    || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    || config.getActivityType() == ACTIVITY_TYPE_HOME) {
+                continue;
+            }
+            addAnimation(task);
+        }
+
+        // Adjust the wallpaper visibility for the showing home activity
+        final AppWindowToken recentsComponentAppToken =
+                dc.getHomeStack().getTopChild().getTopFullscreenAppToken();
+        if (recentsComponentAppToken != null) {
+            if (DEBUG) Log.d(TAG, "setHomeApp(" + recentsComponentAppToken.getName() + ")");
+            mHomeAppToken = recentsComponentAppToken;
+            final WallpaperController wc = dc.mWallpaperController;
+            if (recentsComponentAppToken.windowsCanBeWallpaperTarget()) {
+                dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                dc.setLayoutNeeded();
+            }
+        }
+
+        mService.mWindowPlacerLocked.performSurfacePlacement();
+    }
+
+    private void addAnimation(Task task) {
+        if (DEBUG) Log.d(TAG, "addAnimation(" + task.getName() + ")");
+        final SurfaceAnimator anim = new SurfaceAnimator(task, null /* animationFinishedCallback */,
+                mService.mAnimator::addAfterPrepareSurfacesRunnable, mService);
+        final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task);
+        anim.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */);
+        task.commitPendingTransaction();
+        mPendingAnimations.add(taskAdapter);
+    }
+
+    void startAnimation() {
+        if (DEBUG) Log.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart);
+        if (!mPendingStart) {
+            return;
+        }
+        try {
+            final RemoteAnimationTarget[] appAnimations =
+                    new RemoteAnimationTarget[mPendingAnimations.size()];
+            for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+                appAnimations[i] = mPendingAnimations.get(i).createRemoteAnimationApp();
+            }
+            mPendingStart = false;
+            mRunner.onAnimationStart(mController, appAnimations);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to start recents animation", e);
+        }
+    }
+
+    void cancelAnimation() {
+        if (DEBUG) Log.d(TAG, "cancelAnimation()");
+        if (mCanceled) {
+            // We've already canceled the animation
+            return;
+        }
+        mCanceled = true;
+        try {
+            mRunner.onAnimationCanceled();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to cancel recents animation", e);
+        }
+
+        // Clean up and return to the previous app
+        mCallbacks.onAnimationFinished(false /* moveHomeToTop */);
+    }
+
+    void cleanupAnimation() {
+        if (DEBUG) Log.d(TAG, "cleanupAnimation(): mPendingAnimations="
+                + mPendingAnimations.size());
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
+            adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
+        }
+        mPendingAnimations.clear();
+
+        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        mService.scheduleAnimationLocked();
+    }
+
+    void checkAnimationReady(WallpaperController wallpaperController) {
+        if (mPendingStart) {
+            final boolean wallpaperReady = !isHomeAppOverWallpaper()
+                    || (wallpaperController.getWallpaperTarget() != null
+                            && wallpaperController.wallpaperTransitionReady());
+            if (wallpaperReady) {
+                mService.getRecentsAnimationController().startAnimation();
+            }
+        }
+    }
+
+    boolean isWallpaperVisible(WindowState w) {
+        return w != null && w.mAppToken != null && mHomeAppToken == w.mAppToken
+                && isHomeAppOverWallpaper();
+    }
+
+    boolean isHomeAppOverWallpaper() {
+        if (mHomeAppToken == null) {
+            return false;
+        }
+        return mHomeAppToken.windowsCanBeWallpaperTarget();
+    }
+
+    WindowState getHomeAppMainWindow() {
+        if (mHomeAppToken == null) {
+            return null;
+        }
+        return mHomeAppToken.findMainWindow();
+    }
+
+    boolean isAnimatingApp(AppWindowToken appToken) {
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            final Task task = mPendingAnimations.get(i).mTask;
+            for (int j = task.getChildCount() - 1; j >= 0; j--) {
+                final AppWindowToken app = task.getChildAt(j);
+                if (app == appToken) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean isInputConsumerEnabled() {
+        return mInputConsumerEnabled;
+    }
+
+    private class TaskAnimationAdapter implements AnimationAdapter {
+
+        private Task mTask;
+        private SurfaceControl mCapturedLeash;
+        private OnAnimationFinishedCallback mCapturedFinishCallback;
+
+        TaskAnimationAdapter(Task task) {
+            mTask = task;
+        }
+
+        RemoteAnimationTarget createRemoteAnimationApp() {
+            // TODO: Do we need position and stack bounds?
+            return new RemoteAnimationTarget(mTask.mTaskId, MODE_CLOSING, mCapturedLeash,
+                    !mTask.fillsParent(),
+                    mTask.getTopVisibleAppMainWindow().mWinAnimator.mLastClipRect,
+                    mTask.getPrefixOrderIndex(), new Point(), new Rect(),
+                    mTask.getWindowConfiguration());
+        }
+
+        @Override
+        public boolean getDetachWallpaper() {
+            return false;
+        }
+
+        @Override
+        public int getBackgroundColor() {
+            return 0;
+        }
+
+        @Override
+        public void startAnimation(SurfaceControl animationLeash, Transaction t,
+                OnAnimationFinishedCallback finishCallback) {
+            mCapturedLeash = animationLeash;
+            mCapturedFinishCallback = finishCallback;
+        }
+
+        @Override
+        public void onAnimationCancelled(SurfaceControl animationLeash) {
+            cancelAnimation();
+        }
+
+        @Override
+        public long getDurationHint() {
+            return 0;
+        }
+
+        @Override
+        public long getStatusBarTransitionsStartTime() {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":");
+        pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart);
+        pw.print(innerPrefix); pw.println("mHomeAppToken=" + mHomeAppToken);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 8515dcb..7d4eafb 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -160,7 +160,8 @@
             return new RemoteAnimationTarget(task.mTaskId, getMode(),
                     mCapturedLeash, !mAppWindowToken.fillsParent(),
                     mainWindow.mWinAnimator.mLastClipRect,
-                    mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds);
+                    mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds,
+                    task.getWindowConfiguration());
         }
 
         private int getMode() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2cc96c9..deed7f1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -623,6 +623,13 @@
                         defaultDisplay.pendingLayoutChanges);
         }
 
+        // Defer starting the recents animation until the wallpaper has drawn
+        final RecentsAnimationController recentsAnimationController =
+            mService.getRecentsAnimationController();
+        if (recentsAnimationController != null) {
+            recentsAnimationController.checkAnimationReady(mWallpaperController);
+        }
+
         if (mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0
                 && !mService.mAppTransition.isReady()) {
             // At this point, there was a window with a wallpaper that was force hiding other
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 10f1c3a..0512a08 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -62,7 +62,7 @@
      * @param addAfterPrepareSurfaces Consumer that takes a runnable and executes it after preparing
      *                                surfaces in WM. Can be implemented differently during testing.
      */
-    SurfaceAnimator(Animatable animatable, Runnable animationFinishedCallback,
+    SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback,
             Consumer<Runnable> addAfterPrepareSurfaces, WindowManagerService service) {
         mAnimatable = animatable;
         mService = service;
@@ -71,7 +71,8 @@
                 addAfterPrepareSurfaces);
     }
 
-    private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback,
+    private OnAnimationFinishedCallback getFinishedCallback(
+            @Nullable Runnable animationFinishedCallback,
             Consumer<Runnable> addAfterPrepareSurfaces) {
         return anim -> {
             synchronized (mService.mWindowMap) {
@@ -97,7 +98,9 @@
                     SurfaceControl.openTransaction();
                     try {
                         reset(t, true /* destroyLeash */);
-                        animationFinishedCallback.run();
+                        if (animationFinishedCallback != null) {
+                            animationFinishedCallback.run();
+                        }
                     } finally {
                         SurfaceControl.mergeToGlobalTransaction(t);
                         SurfaceControl.closeTransaction();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 1218d3b..f2ad6fb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -149,8 +149,17 @@
             mFindResults.setUseTopWallpaperAsTarget(true);
         }
 
+        final RecentsAnimationController recentsAnimationController =
+                mService.getRecentsAnimationController();
         final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
-        if (hasWallpaper && w.isOnScreen() && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
+        final boolean isRecentsTransitionTarget = (recentsAnimationController != null
+                && recentsAnimationController.isWallpaperVisible(w));
+        if (isRecentsTransitionTarget) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
+            mFindResults.setWallpaperTarget(w);
+            return true;
+        } else if (hasWallpaper && w.isOnScreen()
+                && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
             if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
             mFindResults.setWallpaperTarget(w);
             if (w == mWallpaperTarget && w.mWinAnimator.isAnimationSet()) {
@@ -199,15 +208,22 @@
         }
     }
 
-    private boolean isWallpaperVisible(WindowState wallpaperTarget) {
+    private final boolean isWallpaperVisible(WindowState wallpaperTarget) {
+        final RecentsAnimationController recentsAnimationController =
+                mService.getRecentsAnimationController();
+        boolean isAnimatingWithRecentsComponent = recentsAnimationController != null
+                && recentsAnimationController.isWallpaperVisible(wallpaperTarget);
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
                 + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
                 + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
                 ? wallpaperTarget.mAppToken.isSelfAnimating() : null)
-                + " prev=" + mPrevWallpaperTarget);
+                + " prev=" + mPrevWallpaperTarget
+                + " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent);
         return (wallpaperTarget != null
-                && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
-                && wallpaperTarget.mAppToken.isSelfAnimating())))
+                && (!wallpaperTarget.mObscured
+                        || isAnimatingWithRecentsComponent
+                        || (wallpaperTarget.mAppToken != null
+                                && wallpaperTarget.mAppToken.isSelfAnimating())))
                 || mPrevWallpaperTarget != null;
     }
 
@@ -587,6 +603,11 @@
             mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT;
             if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
                     "*** WALLPAPER DRAW TIMEOUT");
+
+            // If there was a recents animation in progress, cancel that animation
+            if (mService.getRecentsAnimationController() != null) {
+                mService.getRecentsAnimationController().cancelAnimation();
+            }
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 42c6ec2..a0b59ef 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -337,9 +337,9 @@
     }
 
     /** Returns true if this window container has the input child. */
-    boolean hasChild(WindowContainer child) {
+    boolean hasChild(E child) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer current = mChildren.get(i);
+            final E current = mChildren.get(i);
             if (current == child || current.hasChild(child)) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index de1e7ec..4fb2390 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -24,6 +24,8 @@
 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_USER_HANDLE;
@@ -123,6 +125,7 @@
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IAssistDataReceiver;
+import android.app.WindowConfiguration;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -196,6 +199,7 @@
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
+import android.view.IRecentsAnimationRunner;
 import android.view.IRotationWatcher;
 import android.view.IWallpaperVisibilityListener;
 import android.view.IWindow;
@@ -528,6 +532,7 @@
     IInputMethodManager mInputMethodManager;
 
     AccessibilityController mAccessibilityController;
+    private RecentsAnimationController mRecentsAnimationController;
 
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
@@ -2670,6 +2675,39 @@
         }
     }
 
+    public void initializeRecentsAnimation(
+            IRecentsAnimationRunner recentsAnimationRunner,
+            RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId) {
+        synchronized (mWindowMap) {
+            cancelRecentsAnimation();
+            mRecentsAnimationController = new RecentsAnimationController(this,
+                    recentsAnimationRunner, callbacks, displayId);
+        }
+    }
+
+    public RecentsAnimationController getRecentsAnimationController() {
+        return mRecentsAnimationController;
+    }
+
+    public void cancelRecentsAnimation() {
+        synchronized (mWindowMap) {
+            if (mRecentsAnimationController != null) {
+                // This call will call through to cleanupAnimation() below after the animation is
+                // canceled
+                mRecentsAnimationController.cancelAnimation();
+            }
+        }
+    }
+
+    public void cleanupRecentsAnimation() {
+        synchronized (mWindowMap) {
+            if (mRecentsAnimationController != null) {
+                mRecentsAnimationController.cleanupAnimation();
+                mRecentsAnimationController = null;
+            }
+        }
+    }
+
     public void setAppFullscreen(IBinder token, boolean toOpaque) {
         synchronized (mWindowMap) {
             final AppWindowToken atoken = mRoot.getAppWindowToken(token);
@@ -6327,6 +6365,10 @@
             pw.print("  mSkipAppTransitionAnimation=");pw.println(mSkipAppTransitionAnimation);
             pw.println("  mLayoutToAnim:");
             mAppTransition.dump(pw, "    ");
+            if (mRecentsAnimationController != null) {
+                pw.print("  mRecentsAnimationController="); pw.println(mRecentsAnimationController);
+                mRecentsAnimationController.dump(pw, "    ");
+            }
         }
     }