Prevent unnecessary reordering of the home stack

- Just cancel the recents animation in-place when handling the home button
  to prevent it from being repositioned to the bottom (which stops home)
  and then starting it again (which restarts it)

Bug: 74405472
Test: Press home from an app, ensure launcher stop is only called once
Change-Id: Id41aa2f77c01767cc2c35445458b8f2db81200fc
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 08e5d44..3b22cd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -204,6 +204,8 @@
 import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_HOME_IN_PLACE;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
@@ -257,7 +259,6 @@
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
 import android.app.admin.DevicePolicyCache;
-import android.app.admin.DevicePolicyManager;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.IBackupManager;
@@ -5225,12 +5226,14 @@
     }
 
     @Override
-    public void cancelRecentsAnimation() {
+    public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
         enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()");
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                mWindowManager.cancelRecentsAnimation();
+                mWindowManager.cancelRecentsAnimation(restoreHomeStackPosition
+                        ? REORDER_MOVE_HOME_TO_ORIGINAL_POSITION
+                        : REORDER_KEEP_HOME_IN_PLACE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index 9df321c..5e74619 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -23,6 +23,8 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_TOP;
 
 import android.app.ActivityOptions;
 import android.content.ComponentName;
@@ -32,6 +34,7 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.view.IRecentsAnimationRunner;
+import com.android.server.wm.RecentsAnimationController;
 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
 import com.android.server.wm.WindowManagerService;
 
@@ -123,7 +126,7 @@
 
             // Fetch all the surface controls and pass them to the client to get the animation
             // started
-            mWindowManager.cancelRecentsAnimation();
+            mWindowManager.cancelRecentsAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
             mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this,
                     display.mDisplayId, mStackSupervisor.mRecentTasks.getRecentTaskIds());
 
@@ -140,7 +143,7 @@
     }
 
     @Override
-    public void onAnimationFinished(boolean moveHomeToTop) {
+    public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode) {
         synchronized (mService) {
             if (mWindowManager.getRecentsAnimationController() == null) return;
 
@@ -151,9 +154,8 @@
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
                 mWindowManager.deferSurfaceLayout();
                 try {
-                    mWindowManager.cleanupRecentsAnimation(moveHomeToTop);
+                    mWindowManager.cleanupRecentsAnimation(reorderMode);
 
-                    // Move the home stack to the front
                     final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
                     if (homeActivity == null) {
                         return;
@@ -162,15 +164,19 @@
                     // Restore the launched-behind state
                     homeActivity.mLaunchTaskBehind = false;
 
-                    if (moveHomeToTop) {
+                    if (reorderMode == REORDER_MOVE_HOME_TO_TOP) {
                         // Bring the home stack to the front
                         final ActivityStack homeStack = homeActivity.getStack();
                         mStackSupervisor.mNoAnimActivities.add(homeActivity);
                         homeStack.moveToFront("RecentsAnimation.onAnimationFinished()");
-                    } else {
+                    } else if (reorderMode == REORDER_MOVE_HOME_TO_ORIGINAL_POSITION){
                         // Restore the home stack to its previous position
                         final ActivityDisplay display = homeActivity.getDisplay();
                         display.moveHomeStackBehindStack(mRestoreHomeBehindStack);
+                    } else {
+                        // Keep home stack in place, nothing changes, so ignore the transition logic
+                        // below
+                        return;
                     }
 
                     mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index ba67ff6..112d93c 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -161,7 +161,10 @@
 
         // Timeout callback to ensure we continue the animation if waiting for resuming or app
         // windows drawn fails
-        private final Runnable mResumeRunnable = () -> resume();
+        private final Runnable mResumeRunnable = () -> {
+            if (DEBUG) Slog.d(TAG, "pause: timed out waiting for windows drawn");
+            resume();
+        };
 
         BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
                 @SchedulePipModeChangedState int schedulePipModeChangedState,
@@ -213,7 +216,7 @@
 
                 // When starting an animation from fullscreen, pause here and wait for the
                 // windows-drawn signal before we start the rest of the transition down into PiP.
-                if (mMoveFromFullscreen) {
+                if (mMoveFromFullscreen && mTarget.shouldDeferStartOnMoveToFullscreen()) {
                     pause();
                 }
             } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
index 647a2d6..68be4e8 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
@@ -34,6 +34,12 @@
     void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
 
     /**
+     * @return Whether the animation should be paused waiting for the windows to draw before
+     *         entering PiP.
+     */
+    boolean shouldDeferStartOnMoveToFullscreen();
+
+    /**
      * Sets the size of the target (without any intermediate steps, like scheduling animation)
      * but freezes the bounds of any tasks in the target at taskBounds, to allow for more
      * flexibility during resizing. Only works for the pinned stack at the moment.  This will
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 7274aee..29077f9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -29,6 +29,7 @@
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
 
+import android.annotation.IntDef;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.WindowConfiguration;
 import android.graphics.Point;
@@ -67,12 +68,25 @@
     private static final boolean DEBUG = false;
     private static final long FAILSAFE_DELAY = 1000;
 
+    public static final int REORDER_KEEP_HOME_IN_PLACE = 0;
+    public static final int REORDER_MOVE_HOME_TO_TOP = 1;
+    public static final int REORDER_MOVE_HOME_TO_ORIGINAL_POSITION = 2;
+
+    @IntDef(prefix = { "REORDER_MODE_" }, value = {
+            REORDER_KEEP_HOME_IN_PLACE,
+            REORDER_MOVE_HOME_TO_TOP,
+            REORDER_MOVE_HOME_TO_ORIGINAL_POSITION
+    })
+    public @interface ReorderMode {}
+
     private final WindowManagerService mService;
     private final IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
     private final int mDisplayId;
-    private final Runnable mFailsafeRunnable = this::cancelAnimation;
+    private final Runnable mFailsafeRunnable = () -> {
+        cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
+    };
 
     // The recents component app token that is shown behind the visibile tasks
     private AppWindowToken mHomeAppToken;
@@ -93,7 +107,7 @@
     private Rect mTmpRect = new Rect();
 
     public interface RecentsAnimationCallbacks {
-        void onAnimationFinished(boolean moveHomeToTop);
+        void onAnimationFinished(@ReorderMode int reorderMode);
     }
 
     private final IRecentsAnimationController mController =
@@ -141,7 +155,9 @@
 
                 // Note, the callback will handle its own synchronization, do not lock on WM lock
                 // prior to calling the callback
-                mCallbacks.onAnimationFinished(moveHomeToTop);
+                mCallbacks.onAnimationFinished(moveHomeToTop
+                        ? REORDER_MOVE_HOME_TO_TOP
+                        : REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -222,14 +238,14 @@
 
         // Skip the animation if there is nothing to animate
         if (mPendingAnimations.isEmpty()) {
-            cancelAnimation();
+            cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
             return;
         }
 
         try {
             mRunner.asBinder().linkToDeath(this, 0);
         } catch (RemoteException e) {
-            cancelAnimation();
+            cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
             return;
         }
 
@@ -299,7 +315,7 @@
                 reasons).sendToTarget();
     }
 
-    void cancelAnimation() {
+    void cancelAnimation(@ReorderMode int reorderMode) {
         if (DEBUG) Log.d(TAG, "cancelAnimation()");
         synchronized (mService.getWindowManagerLock()) {
             if (mCanceled) {
@@ -316,16 +332,17 @@
         }
         // Clean up and return to the previous app
         // Don't hold the WM lock here as it calls back to AM/RecentsAnimation
-        mCallbacks.onAnimationFinished(false /* moveHomeToTop */);
+        mCallbacks.onAnimationFinished(reorderMode);
     }
 
-    void cleanupAnimation(boolean moveHomeToTop) {
+    void cleanupAnimation(@ReorderMode int reorderMode) {
         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.mTask.setCanAffectSystemUiFlags(true);
-            if (moveHomeToTop) {
+            if (reorderMode == REORDER_MOVE_HOME_TO_TOP
+                    || reorderMode == REORDER_KEEP_HOME_IN_PLACE) {
                 adapter.mTask.dontAnimateDimExit();
             }
             adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
@@ -344,7 +361,7 @@
 
     @Override
     public void binderDied() {
-        cancelAnimation();
+        cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
     }
 
     void checkAnimationReady(WallpaperController wallpaperController) {
@@ -458,7 +475,7 @@
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
-            cancelAnimation();
+            cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 62754ad..c97f394 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -33,8 +33,6 @@
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.StackProto.ADJUSTED_BOUNDS;
 import static com.android.server.wm.StackProto.ADJUSTED_FOR_IME;
 import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT;
@@ -47,6 +45,8 @@
 import static com.android.server.wm.StackProto.MINIMIZE_AMOUNT;
 import static com.android.server.wm.StackProto.TASKS;
 import static com.android.server.wm.StackProto.WINDOW_CONTAINER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
@@ -62,12 +62,10 @@
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
-
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.server.EventLogTags;
-
 import java.io.PrintWriter;
 
 public class TaskStack extends WindowContainer<Task> implements
@@ -1687,6 +1685,25 @@
         }
     }
 
+    @Override
+    public boolean shouldDeferStartOnMoveToFullscreen() {
+        // Workaround for the recents animation -- normally we need to wait for the new activity to
+        // show before starting the PiP animation, but because we start and show the home activity
+        // early for the recents animation prior to the PiP animation starting, there is no
+        // subsequent all-drawn signal. In this case, we can skip the pause when the home stack is
+        // already visible and drawn.
+        final TaskStack homeStack = mDisplayContent.getHomeStack();
+        if (homeStack == null) {
+            return true;
+        }
+        final Task homeTask = homeStack.getTopChild();
+        final AppWindowToken homeApp = homeTask.getTopVisibleAppToken();
+        if (!homeTask.isVisible() || homeApp == null) {
+            return true;
+        }
+        return !homeApp.allDrawn;
+    }
+
     /**
      * @return True if we are currently animating the pinned stack from fullscreen to non-fullscreen
      *         bounds and we have a deferred PiP mode changed callback set with the animation.
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index c509980..6de1579 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -26,6 +26,7 @@
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
@@ -618,7 +619,8 @@
 
             // If there was a recents animation in progress, cancel that animation
             if (mService.getRecentsAnimationController() != null) {
-                mService.getRecentsAnimationController().cancelAnimation();
+                mService.getRecentsAnimationController().cancelAnimation(
+                        REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
             }
             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 f1cd46b..28a57f5a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2687,20 +2687,20 @@
         }
     }
 
-    public void cancelRecentsAnimation() {
+    public void cancelRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
         // Note: Do not hold the WM lock, this will lock appropriately in the call which also
         // calls through to AM/RecentsAnimation.onAnimationFinished()
         if (mRecentsAnimationController != null) {
             // This call will call through to cleanupAnimation() below after the animation is
             // canceled
-            mRecentsAnimationController.cancelAnimation();
+            mRecentsAnimationController.cancelAnimation(reorderMode);
         }
     }
 
-    public void cleanupRecentsAnimation(boolean moveHomeToTop) {
+    public void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
         synchronized (mWindowMap) {
             if (mRecentsAnimationController != null) {
-                mRecentsAnimationController.cleanupAnimation(moveHomeToTop);
+                mRecentsAnimationController.cleanupAnimation(reorderMode);
                 mRecentsAnimationController = null;
                 mAppTransition.updateBooster();
             }
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 7765387..6290ff1 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -649,7 +649,7 @@
         assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
         assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
                 null));
-        assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation());
+        assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true));
     }
 
     private void testGetTasksApis(boolean expectCallable) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 2bfe274..6019958 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -159,6 +159,11 @@
         }
 
         @Override
+        public boolean shouldDeferStartOnMoveToFullscreen() {
+            return true;
+        }
+
+        @Override
         public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
             // TODO: Once we break the runs apart, we should fail() here if this is called outside
             //       of onAnimationStart() and onAnimationEnd()