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;
}
}