Schedule PIP mode changes at the beginning/end of the transitions.

- When transitioning from fullscreen to PiP, ensure all PiP/MW/Config
  changes come after the transition completes, and inversely, from PiP to
  fullscreen, ensure that all changes come before the transition up starts
- Add a series of tests to verify the callback state when the animation
  is canceled
- Also fixes an issue where the surface is preserved when we don't want

Bug: 37169080
Bug: 37103000
Test: bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
Test: android.server.cts.ActivityManagerPinnedStackTests
Change-Id: I6425c95df358358ed76d9cc8a130606c2124062e
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index f41eed5..e634552 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -22,6 +22,7 @@
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -35,6 +36,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Enables animating bounds of objects.
  *
@@ -43,7 +47,7 @@
  * relaunching it would cause poorer experience), these class provides a way to directly animate
  * the bounds of the resized object.
  *
- * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
+ * The object that is resized needs to implement {@link BoundsAnimationTarget} interface.
  *
  * NOTE: All calls to methods in this class should be done on the UI thread
  */
@@ -56,8 +60,19 @@
 
     private static final int DEFAULT_TRANSITION_DURATION = 425;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START,
+        SCHEDULE_PIP_MODE_CHANGED_ON_END})
+    public @interface SchedulePipModeChangedState {}
+    /** Do not schedule any PiP mode changed callbacks as a part of this animation. */
+    public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0;
+    /** Schedule a PiP mode changed callback when this animation starts. */
+    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1;
+    /** Schedule a PiP mode changed callback when this animation ends. */
+    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;
+
     // Only accessed on UI thread.
-    private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
+    private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
 
     private final class AppTransitionNotifier
             extends WindowManagerInternal.AppTransitionListener implements Runnable {
@@ -108,40 +123,42 @@
     @VisibleForTesting
     final class BoundsAnimator extends ValueAnimator
             implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
-        private final AnimateBoundsUser mTarget;
+        private final BoundsAnimationTarget mTarget;
         private final Rect mFrom = new Rect();
         private final Rect mTo = new Rect();
         private final Rect mTmpRect = new Rect();
         private final Rect mTmpTaskBounds = new Rect();
-        private final boolean mMoveToFullScreen;
-        // True if this this animation was cancelled and will be replaced the another animation from
-        // the same {@link #AnimateBoundsUser} target.
+
+        // True if this this animation was canceled and will be replaced the another animation from
+        // the same {@link #BoundsAnimationTarget} target.
         private boolean mSkipFinalResize;
         // True if this animation replaced a previous animation of the same
-        // {@link #AnimateBoundsUser} target.
+        // {@link #BoundsAnimationTarget} target.
         private final boolean mSkipAnimationStart;
-        // True if this animation was cancelled by the user, not as a part of a replacing animation
+        // True if this animation was canceled by the user, not as a part of a replacing animation
         private boolean mSkipAnimationEnd;
-        // True if this animation is not replacing a previous animation, or if the previous
-        // animation is animating to a different fullscreen state than the current animation.
-        // We use this to ensure that we always provide a consistent set/order of callbacks when we
-        // transition to/from PiP.
-        private final boolean mAnimatingToNewFullscreenState;
+        // True if the animation target should be moved to the fullscreen stack at the end of this
+        // animation
+        private boolean mMoveToFullscreen;
+
+        // Whether to schedule PiP mode changes on animation start/end
+        private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
 
         // Depending on whether we are animating from
         // a smaller to a larger size
         private final int mFrozenTaskWidth;
         private final int mFrozenTaskHeight;
 
-        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen,
-                boolean replacingExistingAnimation, boolean animatingToNewFullscreenState) {
+        BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
+                @SchedulePipModeChangedState int schedulePipModeChangedState,
+                boolean moveToFullscreen, boolean replacingExistingAnimation) {
             super();
             mTarget = target;
             mFrom.set(from);
             mTo.set(to);
-            mMoveToFullScreen = moveToFullScreen;
             mSkipAnimationStart = replacingExistingAnimation;
-            mAnimatingToNewFullscreenState = animatingToNewFullscreenState;
+            mSchedulePipModeChangedState = schedulePipModeChangedState;
+            mMoveToFullscreen = moveToFullscreen;
             addUpdateListener(this);
             addListener(this);
 
@@ -161,7 +178,8 @@
         @Override
         public void onAnimationStart(Animator animation) {
             if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
-                    + " mSkipAnimationStart=" + mSkipAnimationStart);
+                    + " mSkipAnimationStart=" + mSkipAnimationStart
+                    + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
             mFinishAnimationAfterTransition = false;
             mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
                     mFrom.top + mFrozenTaskHeight);
@@ -170,13 +188,8 @@
             // we trigger any size changes, so it can swap surfaces
             // in to appropriate modes, or do as it wishes otherwise.
             if (!mSkipAnimationStart) {
-                mTarget.onAnimationStart(mMoveToFullScreen);
-            }
-
-            // If we are animating to a new fullscreen state (either to/from fullscreen), then
-            // notify the target of the change with the new frozen task bounds
-            if (mAnimatingToNewFullscreenState && mMoveToFullScreen) {
-                mTarget.updatePictureInPictureMode(null);
+                mTarget.onAnimationStart(mSchedulePipModeChangedState ==
+                        SCHEDULE_PIP_MODE_CHANGED_ON_START);
             }
 
             // Immediately update the task bounds if they have to become larger, but preserve
@@ -206,20 +219,26 @@
                 // any further animation.
                 if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
 
+                // If we have already scheduled a PiP mode changed at the start of the animation,
+                // then we need to clean up and schedule one at the end, since we have canceled the
+                // animation to the final state.
+                if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                    mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+                }
+
                 // Since we are cancelling immediately without a replacement animation, send the
                 // animation end to maintain callback parity, but also skip any further resizes
-                prepareCancel(false /* skipAnimationEnd */, true /* skipFinalResize */);
-                cancel();
+                cancelAndCallAnimationEnd();
             }
         }
 
         @Override
         public void onAnimationEnd(Animator animation) {
             if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
-                    + " mMoveToFullScreen=" + mMoveToFullScreen
                     + " mSkipFinalResize=" + mSkipFinalResize
                     + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
-                    + " mAppTransitionIsRunning=" + mAppTransition.isRunning());
+                    + " mAppTransitionIsRunning=" + mAppTransition.isRunning()
+                    + " callers=" + Debug.getCallers(2));
 
             // There could be another animation running. For example in the
             // move to fullscreen case, recents will also be closing while the
@@ -231,58 +250,57 @@
                 return;
             }
 
-            if (!mSkipFinalResize) {
-                // If not cancelled, resize the pinned stack to the final size. All calls to
-                // setPinnedStackSize() must be done between onAnimationStart() and onAnimationEnd()
-                mTarget.setPinnedStackSize(mTo, null);
+            if (!mSkipAnimationEnd) {
+                // If this animation has already scheduled the picture-in-picture mode on start, and
+                // we are not skipping the final resize due to being canceled, then move the PiP to
+                // fullscreen once the animation ends
+                if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
+                        + " moveToFullscreen=" + mMoveToFullscreen);
+                mTarget.onAnimationEnd(mSchedulePipModeChangedState ==
+                        SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null,
+                                mMoveToFullscreen);
             }
 
-            finishAnimation();
-
-            if (mMoveToFullScreen && !mSkipFinalResize) {
-                mTarget.moveToFullscreen();
-            }
+            // Clean up this animation
+            removeListener(this);
+            removeUpdateListener(this);
+            mRunningAnimations.remove(mTarget);
         }
 
         @Override
         public void onAnimationCancel(Animator animation) {
-            finishAnimation();
+            // Always skip the final resize when the animation is canceled
+            mSkipFinalResize = true;
+            mMoveToFullscreen = false;
         }
 
-        public void prepareCancel(boolean skipAnimationEnd, boolean skipFinalResize) {
-            if (DEBUG) Slog.d(TAG, "prepareCancel: skipAnimationEnd=" + skipAnimationEnd
-                    + " skipFinalResize=" + skipFinalResize);
-            mSkipAnimationEnd = skipAnimationEnd;
-            mSkipFinalResize = skipFinalResize;
+        private void cancelAndCallAnimationEnd() {
+            if (DEBUG) Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget);
+            mSkipAnimationEnd = false;
+            super.cancel();
         }
 
         @Override
         public void cancel() {
             if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget);
+            mSkipAnimationEnd = true;
             super.cancel();
         }
 
-        /** Returns true if the animation target is the same as the input bounds. */
+        /**
+         * @return true if the animation target is the same as the input bounds.
+         */
         boolean isAnimatingTo(Rect bounds) {
             return mTo.equals(bounds);
         }
 
-        private boolean animatingToLargerSize() {
-            if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
-                return false;
-            }
-            return true;
-        }
-
-        private void finishAnimation() {
-            if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
-                    + " callers" + Debug.getCallers(2));
-            if (!mSkipAnimationEnd) {
-                mTarget.onAnimationEnd();
-            }
-            removeListener(this);
-            removeUpdateListener(this);
-            mRunningAnimations.remove(mTarget);
+        /**
+         * @return true if we are animating to a larger surface size
+         */
+        @VisibleForTesting
+        boolean animatingToLargerSize() {
+            // TODO: Fix this check for aspect ratio changes
+            return (mFrom.width() * mFrom.height() <= mTo.width() * mTo.height());
         }
 
         @Override
@@ -291,63 +309,23 @@
         }
     }
 
-    public interface AnimateBoundsUser {
-        /**
-         * 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.
-         *
-         * @return Whether the target should continue to be animated and this call was successful.
-         * If false, the animation will be cancelled because the user has determined that the
-         * animation is now invalid and not required. In such a case, the cancel will trigger the
-         * animation end callback as well, but will not send any further size changes.
-         */
-        boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
-
-        /**
-         * Callback for the target to inform it that the animation has started, so it can do some
-         * necessary preparation.
-         */
-        void onAnimationStart(boolean toFullscreen);
-
-        /**
-         * Callback for the target to inform it that the animation is going to a new fullscreen
-         * state and should update the picture-in-picture mode accordingly.
-         *
-         * @param targetStackBounds the target stack bounds we are animating to, can be null if
-         *                          we are animating to fullscreen
-         */
-        void updatePictureInPictureMode(Rect targetStackBounds);
-
-        /**
-         * Callback for the target to inform it that the animation has ended, so it can do some
-         * necessary cleanup.
-         */
-        void onAnimationEnd();
-
-        /**
-         * Callback for the target to inform it to reparent to the fullscreen stack.
-         */
-        void moveToFullscreen();
-    }
-
-    public void animateBounds(final AnimateBoundsUser target, Rect from, Rect to,
-            int animationDuration, boolean moveToFullscreen) {
-        animateBoundsImpl(target, from, to, animationDuration, moveToFullscreen);
+    public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
+            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
+            boolean moveToFullscreen) {
+        animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
+                moveToFullscreen);
     }
 
     @VisibleForTesting
-    BoundsAnimator animateBoundsImpl(final AnimateBoundsUser target, Rect from, Rect to,
-            int animationDuration, boolean moveToFullscreen) {
+    BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
+            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
+            boolean moveToFullscreen) {
         final BoundsAnimator existing = mRunningAnimations.get(target);
         final boolean replacing = existing != null;
-        final boolean animatingToNewFullscreenState = (existing == null) ||
-                (existing.mMoveToFullScreen != moveToFullscreen);
 
         if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
-                + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing
-                + " animatingToNewFullscreenState=" + animatingToNewFullscreenState);
+                + " schedulePipModeChangedState=" + schedulePipModeChangedState
+                + " replacing=" + replacing);
 
         if (replacing) {
             if (existing.isAnimatingTo(to)) {
@@ -355,15 +333,36 @@
                 // one we are trying to start.
                 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
                         + " ignoring...");
+
                 return existing;
             }
-            // Since we are replacing, we skip both animation start and end callbacks, and don't
-            // animate to the final bounds when cancelling
-            existing.prepareCancel(true /* skipAnimationEnd */, true /* skipFinalResize */);
+
+            // Update the PiP callback states if we are replacing the animation
+            if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: still animating to fullscreen, keep"
+                            + " existing deferred state");
+                } else {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback"
+                            + " on start already processed, schedule deferred update on end");
+                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+                }
+            } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) {
+                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: non-fullscreen animation canceled,"
+                            + " callback on start will be processed");
+                } else {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: still animating from fullscreen, keep"
+                            + " existing deferred state");
+                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+                }
+            }
+
+            // Since we are replacing, we skip both animation start and end callbacks
             existing.cancel();
         }
-        final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen,
-                replacing, animatingToNewFullscreenState);
+        final BoundsAnimator animator = new BoundsAnimator(target, from, to,
+                schedulePipModeChangedState, moveToFullscreen, replacing);
         mRunningAnimations.put(target, animator);
         animator.setFloatValues(0f, 1f);
         animator.setDuration((animationDuration != -1 ? animationDuration