Ensure that windows are drawn before starting transition into PiP.

- Building upon ag/2125930, we ensure that all windows are drawn before
  starting the enter PiP animation.

Bug: 37420370
Test: bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
Test: android.server.cts.ActivityManagerPinnedStackTests

Change-Id: I73fb71681f62bbc684efedbd3d40c3e1a670db46
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bb6637d..fbab26a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10589,8 +10589,7 @@
                                 mStackSupervisor.getStack(PINNED_STACK_ID);
                         if (pinnedStack != null) {
                             pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
-                                    destBounds, animationDuration,
-                                    false /* schedulePipModeChangedOnAnimationEnd */);
+                                    destBounds, animationDuration, false /* fromFullscreen */);
                         }
                     } else {
                         throw new IllegalArgumentException("Stack: " + stackId
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 79ea7ba..e180aef 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2986,18 +2986,21 @@
             mWindowManager.continueSurfaceLayout();
         }
 
-        // The task might have already been running and its visibility needs to be synchronized
-        // with the visibility of the stack / windows.
+        // Calculate the default bounds (don't use existing stack bounds as we may have just created
+        // the stack, and schedule the start of the animation into PiP (the bounds animator that
+        // is triggered by this is posted on another thread)
+        final Rect destBounds = stack.getPictureInPictureBounds(aspectRatio,
+                false /* useExistingStackBounds */);
+        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
+                true /* fromFullscreen */);
+
+        // Update the visibility of all activities after the they have been reparented to the new
+        // stack.  This MUST run after the animation above is scheduled to ensure that the windows
+        // drawn signal is scheduled after the bounds animation start call on the bounds animator
+        // thread.
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        // Calculate the default bounds (don't use existing stack bounds as we may have just created
-        // the stack
-        final Rect destBounds = stack.getPictureInPictureBounds(aspectRatio,
-                false /* useExistingStackBounds */);
-
-        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
-                true /* schedulePipModeChangedOnAnimationEnd */);
         mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName,
                 r.getTask().taskId);
     }
diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java
index 34cdb54..702bf92 100644
--- a/services/core/java/com/android/server/am/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/am/PinnedActivityStack.java
@@ -50,12 +50,12 @@
     }
 
     void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
-            boolean schedulePipModeChangedOnAnimationEnd) {
+            boolean fromFullscreen) {
         if (skipResizeAnimation(toBounds == null /* toFullscreen */)) {
             mService.moveTasksToFullscreenStack(mStackId, true /* onTop */);
         } else {
             getWindowContainerController().animateResizePinnedStack(toBounds, sourceHintBounds,
-                    animationDuration, schedulePipModeChangedOnAnimationEnd);
+                    animationDuration, fromFullscreen);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 17db253..4e38e3d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.StackId;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -51,6 +52,7 @@
 
 import android.annotation.NonNull;
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -1343,9 +1345,11 @@
                 }
                 mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN, token).sendToTarget();
 
-                final TaskStack s = getStack();
-                if (s != null) {
-                    s.onAllWindowsDrawn();
+                // Notify the pinned stack upon all windows drawn. If there was an animation in
+                // progress then this signal will resume that animation.
+                final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+                if (pinnedStack != null) {
+                    pinnedStack.onAllWindowsDrawn();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 7f3c89c..410efcd 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -139,8 +139,14 @@
         private final boolean mSkipAnimationStart;
         // True if this animation was canceled by the user, not as a part of a replacing animation
         private boolean mSkipAnimationEnd;
+
+        // True if the animation target is animating from the fullscreen. Only one of
+        // {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be true at any time in the
+        // animation.
+        private boolean mMoveFromFullscreen;
         // True if the animation target should be moved to the fullscreen stack at the end of this
-        // animation
+        // animation. Only one of {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be
+        // true at any time in the animation.
         private boolean mMoveToFullscreen;
 
         // Whether to schedule PiP mode changes on animation start/end
@@ -151,15 +157,21 @@
         private final int mFrozenTaskWidth;
         private final int mFrozenTaskHeight;
 
+        // Timeout callback to ensure we continue the animation if waiting for resuming or app
+        // windows drawn fails
+        private final Runnable mResumeRunnable = () -> resume();
+
         BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
                 @SchedulePipModeChangedState int schedulePipModeChangedState,
-                boolean moveToFullscreen, boolean replacingExistingAnimation) {
+                boolean moveFromFullscreen, boolean moveToFullscreen,
+                boolean replacingExistingAnimation) {
             super();
             mTarget = target;
             mFrom.set(from);
             mTo.set(to);
             mSkipAnimationStart = replacingExistingAnimation;
             mSchedulePipModeChangedState = schedulePipModeChangedState;
+            mMoveFromFullscreen = moveFromFullscreen;
             mMoveToFullscreen = moveToFullscreen;
             addUpdateListener(this);
             addListener(this);
@@ -177,13 +189,6 @@
             }
         }
 
-        final Runnable mResumeRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    resume();
-                }
-        };
-
         @Override
         public void onAnimationStart(Animator animation) {
             if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
@@ -199,6 +204,12 @@
             if (!mSkipAnimationStart) {
                 mTarget.onAnimationStart(mSchedulePipModeChangedState ==
                         SCHEDULE_PIP_MODE_CHANGED_ON_START);
+
+                // 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) {
+                    pause();
+                }
             }
 
             // Immediately update the task bounds if they have to become larger, but preserve
@@ -213,13 +224,20 @@
                 // correct logic to make this resize seamless.
                 if (mMoveToFullscreen) {
                     pause();
-                    mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
                 }
             }
         }
 
         @Override
+        public void pause() {
+            if (DEBUG) Slog.d(TAG, "pause: waiting for windows drawn");
+            super.pause();
+            mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
+        }
+
+        @Override
         public void resume() {
+            if (DEBUG) Slog.d(TAG, "resume:");
             mHandler.removeCallbacks(mResumeRunnable);
             super.resume();
         }
@@ -336,15 +354,15 @@
 
     public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
             int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
-            boolean moveToFullscreen) {
+            boolean moveFromFullscreen, boolean moveToFullscreen) {
         animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
-                moveToFullscreen);
+                moveFromFullscreen, moveToFullscreen);
     }
 
     @VisibleForTesting
     BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
             int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
-            boolean moveToFullscreen) {
+            boolean moveFromFullscreen, boolean moveToFullscreen) {
         final BoundsAnimator existing = mRunningAnimations.get(target);
         final boolean replacing = existing != null;
 
@@ -387,7 +405,7 @@
             existing.cancel();
         }
         final BoundsAnimator animator = new BoundsAnimator(target, from, to,
-                schedulePipModeChangedState, moveToFullscreen, replacing);
+                schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing);
         mRunningAnimations.put(target, animator);
         animator.setFloatValues(0f, 1f);
         animator.setDuration((animationDuration != -1 ? animationDuration
@@ -397,14 +415,19 @@
         return animator;
     }
 
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    public void onAllWindowsDrawn() {
+        if (DEBUG) Slog.d(TAG, "onAllWindowsDrawn:");
+        mHandler.post(this::resume);
+    }
+
     private void resume() {
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             final BoundsAnimator b = mRunningAnimations.valueAt(i);
             b.resume();
         }
     }
-
-    public void onAllWindowsDrawn() {
-        mHandler.post(this::resume);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index b0b93ab..0c628ac 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -83,7 +83,7 @@
      * Animates the pinned stack.
      */
     public void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
-            int animationDuration, boolean schedulePipModeChangedOnAnimationEnd) {
+            int animationDuration, boolean fromFullscreen) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
                 throw new IllegalArgumentException("Pinned stack container not found :(");
@@ -98,7 +98,7 @@
                 NO_PIP_MODE_CHANGED_CALLBACKS;
             final boolean toFullscreen = toBounds == null;
             if (toFullscreen) {
-                if (schedulePipModeChangedOnAnimationEnd) {
+                if (fromFullscreen) {
                     throw new IllegalArgumentException("Should not defer scheduling PiP mode"
                             + " change on animation to fullscreen.");
                 }
@@ -113,7 +113,7 @@
                     toBounds = new Rect();
                     mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
                 }
-            } else if (schedulePipModeChangedOnAnimationEnd) {
+            } else if (fromFullscreen) {
                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
             }
 
@@ -122,13 +122,13 @@
             final Rect finalToBounds = toBounds;
             final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
                 schedulePipModeChangedState;
-            UiThread.getHandler().post(() -> {
+            mService.mBoundsAnimationController.getHandler().post(() -> {
                 if (mContainer == null) {
                     return;
                 }
                 mService.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
                         finalToBounds, animationDuration, finalSchedulePipModeChangedState,
-                        toFullscreen);
+                        fromFullscreen, toFullscreen);
             });
         }
     }
@@ -152,7 +152,7 @@
             if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
                 if (!toBounds.equals(targetBounds)) {
                     animateResizePinnedStack(toBounds, null /* sourceHintBounds */,
-                            -1 /* duration */, false /* schedulePipModeChangedOnAnimationEnd */);
+                            -1 /* duration */, false /* fromFullscreen */);
                 }
                 pinnedStackController.setAspectRatio(
                         pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9d48ce5..34a0cd6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1488,7 +1488,7 @@
     }
 
     void onAllWindowsDrawn() {
-        if (!mBoundsAnimating) {
+        if (!mBoundsAnimating && !mBoundsAnimatingRequested) {
             return;
         }
 
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 cd7a7c7..ee09f4b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -152,8 +152,6 @@
             mAwaitingAnimationStart = false;
             mAnimationStarted = true;
             mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
-
-            mController.onAllWindowsDrawn();
         }
 
         @Override
@@ -207,6 +205,9 @@
                 throw new IllegalArgumentException("Call restart() to restart an animation");
             }
 
+            boolean fromFullscreen = from.equals(BOUNDS_FULL);
+            boolean toFullscreen = to.equals(BOUNDS_FULL);
+
             mTarget.initialize(from);
 
             // Started, not running
@@ -215,6 +216,15 @@
 
             startImpl(from, to);
 
+            // Ensure that the animator is paused for the all windows drawn signal when animating
+            // to/from fullscreen
+            if (fromFullscreen || toFullscreen) {
+                assertTrue(mAnimator.isPaused());
+                mController.onAllWindowsDrawn();
+            } else {
+                assertTrue(!mAnimator.isPaused());
+            }
+
             // Started and running
             assertTrue(!mTarget.mAwaitingAnimationStart);
             assertTrue(mTarget.mAnimationStarted);
@@ -262,7 +272,7 @@
                             ? SCHEDULE_PIP_MODE_CHANGED_ON_END
                             : NO_PIP_MODE_CHANGED_CALLBACKS;
             mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
-                    schedulePipModeChangedState, toFullscreen);
+                    schedulePipModeChangedState, fromFullscreen, toFullscreen);
 
             // Original stack bounds, frozen task bounds
             assertEquals(mFrom, mTarget.mStackBounds);