Lock free app animations (3/n): Implement transfering animations

Test: go/wm-smoke
Bug: 64674361
Change-Id: I2170c31c1a95cea049bcc66978bb7737337503b3
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index da144e6..dcf2a87 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1126,7 +1126,12 @@
                 }
                 setClientHidden(fromToken.mClientHidden);
 
-                // TODO: Transfer animation
+                transferAnimation(fromToken);
+
+                // When transferring an animation, we no longer need to apply an animation to the
+                // the token we transfer the animation over. Thus, remove the animation from
+                // pending opening apps.
+                mService.mOpeningApps.remove(this);
 
                 mService.updateFocusedWindowLocked(
                         UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 3ef9b3f..a63742e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -158,6 +158,7 @@
                     mRunningAnimations.remove(a.mLeash);
                     synchronized (mCancelLock) {
                         if (!a.mCancelled) {
+
                             // Post on other thread that we can push final state without jank.
                             AnimationThread.getHandler().post(a.mFinishCallback);
                         }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index e165211..bda5bc9 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -22,10 +22,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 
 /**
@@ -41,7 +44,9 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
     private final WindowManagerService mService;
     private AnimationAdapter mAnimation;
-    private SurfaceControl mLeash;
+
+    @VisibleForTesting
+    SurfaceControl mLeash;
     private final Animatable mAnimatable;
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     private final Runnable mAnimationFinishedCallback;
@@ -62,6 +67,11 @@
     private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback) {
         return anim -> {
             synchronized (mService.mWindowMap) {
+                final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
+                if (target != null) {
+                    target.mInnerAnimationFinishedCallback.onAnimationFinished(anim);
+                    return;
+                }
                 if (anim != mAnimation) {
                     // Callback was from another animation - ignore.
                     return;
@@ -70,7 +80,7 @@
                 final Transaction t = new Transaction();
                 SurfaceControl.openTransaction();
                 try {
-                    reset(t);
+                    reset(t, true /* destroyLeash */);
                     animationFinishedCallback.run();
                 } finally {
                     // TODO: This should use pendingTransaction eventually, but right now things
@@ -95,7 +105,7 @@
      *               handing it to the component that is responsible to run the animation.
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        cancelAnimation(t, true /* restarting */);
+        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mAnimation = anim;
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         if (surface == null) {
@@ -158,7 +168,8 @@
      * Cancels any currently running animation.
      */
     void cancelAnimation() {
-        cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */);
+        cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */,
+                true /* forwardCancel */);
         mAnimatable.commitPendingTransaction();
     }
 
@@ -197,13 +208,47 @@
         return mLeash != null;
     }
 
-    private void cancelAnimation(Transaction t, boolean restarting) {
+    void transferAnimation(SurfaceAnimator from) {
+        if (from.mLeash == null) {
+            return;
+        }
+        final SurfaceControl surface = mAnimatable.getSurfaceControl();
+        final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
+        if (surface == null || parent == null) {
+            Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
+            cancelAnimation();
+            return;
+        }
+        endDelayingAnimationStart();
+        final Transaction t = mAnimatable.getPendingTransaction();
+        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
+        mLeash = from.mLeash;
+        mAnimation = from.mAnimation;
+
+        // Cancel source animation, but don't let animation runner cancel the animation.
+        from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
+        t.reparent(surface, mLeash.getHandle());
+        t.reparent(mLeash, parent.getHandle());
+        mAnimatable.onAnimationLeashCreated(t, mLeash);
+        mService.mAnimationTransferMap.put(mAnimation, this);
+    }
+
+    /**
+     * Cancels the animation, and resets the leash.
+     *
+     * @param t The transaction to use for all cancelling surface operations.
+     * @param restarting Whether we are restarting the animation.
+     * @param forwardCancel Whether to forward the cancel signal to the adapter executing the
+     *                      animation. This will be set to false when just transferring an animation
+     *                      to another animator.
+     */
+    private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) {
         if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
         final SurfaceControl leash = mLeash;
         final AnimationAdapter animation = mAnimation;
-        reset(t);
+        reset(t, forwardCancel);
         if (animation != null) {
-            if (!mAnimationStartDelayed) {
+            if (!mAnimationStartDelayed && forwardCancel) {
                 animation.onAnimationCancelled(leash);
             }
             if (!restarting) {
@@ -215,7 +260,7 @@
         }
     }
 
-    private void reset(Transaction t) {
+    private void reset(Transaction t, boolean destroyLeash) {
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
 
@@ -225,7 +270,8 @@
             if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
             t.reparent(surface, parent.getHandle());
         }
-        if (mLeash != null) {
+        mService.mAnimationTransferMap.remove(mAnimation);
+        if (mLeash != null && destroyLeash) {
             mAnimatable.destroyAfterPendingTransaction(mLeash);
         }
         mLeash = null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a0c704e..af06900 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -997,6 +997,10 @@
         mSurfaceAnimator.startAnimation(t, anim, hidden);
     }
 
+    void transferAnimation(WindowContainer from) {
+        mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator);
+    }
+
     void cancelAnimation() {
         mSurfaceAnimator.cancelAnimation();
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9678826..6e2dbe1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -177,6 +177,7 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -767,6 +768,11 @@
     final WindowAnimator mAnimator;
     final SurfaceAnimationRunner mSurfaceAnimationRunner;
 
+    /**
+     * Keeps track of which animations got transferred to which animators. Entries will get cleaned
+     * up when the animation finishes.
+     */
+    final ArrayMap<AnimationAdapter, SurfaceAnimator> mAnimationTransferMap = new ArrayMap<>();
     final BoundsAnimationController mBoundsAnimationController;
 
     private final PointerEventDispatcher mPointerEventDispatcher;
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index ae3113b..759894b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
 
         mToken.setFillsParent(true);
-        token.setHidden(true);
+        mToken.setHidden(true);
         mToken.sendingToBottom = true;
         // Can not specify orientation if app isn't visible even though it fills parent.
         assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
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 53a5899..2bfe274 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -110,7 +110,7 @@
         }
 
         public void notifyTransitionStarting(int transit) {
-            mListener.onAppTransitionStartingLocked(transit, null, null, null, null);
+            mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
         }
 
         public void notifyTransitionFinished() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6f739ca..96ff461 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -61,12 +62,14 @@
 
     private SurfaceSession mSession = new SurfaceSession();
     private MyAnimatable mAnimatable;
+    private MyAnimatable mAnimatable2;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
         mAnimatable = new MyAnimatable();
+        mAnimatable2 = new MyAnimatable();
     }
 
     @Test
@@ -74,15 +77,12 @@
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertAnimating(mAnimatable);
         verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         callbackCaptor.getValue().onAnimationFinished(mSpec);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
         // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
@@ -99,8 +99,7 @@
 
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
-        assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+        assertAnimating(mAnimatable);
         verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
 
         // First animation was finished, but this shouldn't cancel the second animation
@@ -110,16 +109,16 @@
         // Second animation was finished
         verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture());
         callbackCaptor.getValue().onAnimationFinished(mSpec2);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
     }
 
     @Test
     public void testCancelAnimation() throws Exception {
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         verify(mSpec).onAnimationCancelled(any());
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
@@ -130,7 +129,7 @@
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         verifyZeroInteractions(mSpec);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
         verify(mSpec).startAnimation(any(), any(), any());
     }
@@ -141,11 +140,41 @@
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
         verifyZeroInteractions(mSpec);
-        assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+        assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
         assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
     }
 
+    @Test
+    public void testTransferAnimation() throws Exception {
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+
+        final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+                OnAnimationFinishedCallback.class);
+        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        final SurfaceControl leash = mAnimatable.mLeash;
+
+        mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator);
+        assertNotAnimating(mAnimatable);
+        assertAnimating(mAnimatable2);
+        assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
+        assertFalse(mAnimatable.mPendingDestroySurfaces.contains(leash));
+        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        assertNotAnimating(mAnimatable2);
+        assertTrue(mAnimatable2.mFinishedCallbackCalled);
+        assertTrue(mAnimatable2.mPendingDestroySurfaces.contains(leash));
+    }
+
+    private void assertAnimating(MyAnimatable animatable) {
+        assertTrue(animatable.mSurfaceAnimator.isAnimating());
+        assertNotNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
+    private void assertNotAnimating(MyAnimatable animatable) {
+        assertFalse(animatable.mSurfaceAnimator.isAnimating());
+        assertNull(animatable.mSurfaceAnimator.getAnimation());
+    }
+
     private class MyAnimatable implements Animatable {
 
         final SurfaceControl mParent;
@@ -223,8 +252,6 @@
             return 1;
         }
 
-        private final Runnable mFinishedCallback = () -> {
-            mFinishedCallbackCalled = true;
-        };
+        private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true;
     }
 }