summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/animation/AnimationHandler.java6
-rw-r--r--core/java/android/animation/Animator.java33
-rw-r--r--core/java/android/animation/AnimatorSet.java7
-rw-r--r--core/java/android/animation/ValueAnimator.java1
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java38
-rw-r--r--core/tests/coretests/src/android/animation/ValueAnimatorTests.java33
6 files changed, 115 insertions, 3 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index d84a4c12a2cd..d5b2f980e1a6 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -384,6 +384,12 @@ public class AnimationHandler {
});
}
+ void removePendingEndAnimationCallback(Runnable notifyEndAnimation) {
+ if (mPendingEndAnimationListeners != null) {
+ mPendingEndAnimationListeners.remove(notifyEndAnimation);
+ }
+ }
+
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 4bf87f91cb2f..e62cd556af9a 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -82,6 +82,12 @@ public abstract class Animator implements Cloneable {
static boolean sPostNotifyEndListenerEnabled;
/**
+ * If {@link #sPostNotifyEndListenerEnabled} is enabled, it will be set when the end callback
+ * is scheduled. It is cleared when it runs or finishes immediately, e.g. cancel.
+ */
+ private Runnable mPendingEndCallback;
+
+ /**
* A cache of the values in a list. Used so that when calling the list, we have a copy
* of it in case the list is modified while iterating. The array can be reused to avoid
* allocation on every notification.
@@ -660,10 +666,33 @@ public abstract class Animator implements Cloneable {
}
}
+ /**
+ * This is called when the animator needs to finish immediately. This is usually no-op unless
+ * {@link #sPostNotifyEndListenerEnabled} is enabled and a finish request calls around the last
+ * animation frame.
+ *
+ * @param notifyListeners Whether to invoke {@link AnimatorListener#onAnimationEnd}.
+ * @return {@code true} if the pending listeners are removed.
+ */
+ boolean consumePendingEndListeners(boolean notifyListeners) {
+ if (mPendingEndCallback == null) {
+ return false;
+ }
+ AnimationHandler.getInstance().removePendingEndAnimationCallback(mPendingEndCallback);
+ mPendingEndCallback = null;
+ if (notifyListeners) {
+ notifyEndListeners(false /* isReversing */);
+ }
+ return true;
+ }
+
void notifyEndListenersFromEndAnimation(boolean isReversing, boolean postNotifyEndListener) {
if (postNotifyEndListener) {
- AnimationHandler.getInstance().postEndAnimationCallback(
- () -> completeEndAnimation(isReversing, "postNotifyAnimEnd"));
+ mPendingEndCallback = () -> {
+ completeEndAnimation(isReversing, "postNotifyAnimEnd");
+ mPendingEndCallback = null;
+ };
+ AnimationHandler.getInstance().postEndAnimationCallback(mPendingEndCallback);
} else {
completeEndAnimation(isReversing, "notifyAnimEnd");
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 78566d2fe98d..4a07de0410ae 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -423,6 +423,13 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
notifyListeners(AnimatorCaller.ON_CANCEL, false);
callOnPlayingSet(Animator::cancel);
mPlayingSet.clear();
+ // If the end callback is pending, invoke the end callbacks of the animator nodes before
+ // ending this set. Pass notifyListeners=false because this endAnimation will do that.
+ if (consumePendingEndListeners(false /* notifyListeners */)) {
+ for (int i = mNodeMap.size() - 1; i >= 0; i--) {
+ mNodeMap.keyAt(i).consumePendingEndListeners(true /* notifyListeners */);
+ }
+ }
endAnimation();
}
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 492c2ffc561f..fbcc73ea59e7 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1182,6 +1182,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
// If end has already been requested, through a previous end() or cancel() call, no-op
// until animation starts again.
if (mAnimationEndRequested) {
+ consumePendingEndListeners(true /* notifyListeners */);
return;
}
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index 33a46d0fde17..6d86bd209a3d 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.os.Handler;
+import android.os.Looper;
import android.util.PollingCheck;
import android.view.View;
@@ -486,6 +488,42 @@ public class AnimatorSetCallsTest {
});
}
+ @Test
+ public void testCancelOnPendingEndListener() throws Throwable {
+ final CountDownLatch endLatch = new CountDownLatch(1);
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final boolean[] endCalledRightAfterCancel = new boolean[2];
+ final AnimatorSet set = new AnimatorSet();
+ final ValueAnimatorTests.MyListener asListener = new ValueAnimatorTests.MyListener();
+ final ValueAnimatorTests.MyListener vaListener = new ValueAnimatorTests.MyListener();
+ final ValueAnimator va = new ValueAnimator();
+ va.setFloatValues(0f, 1f);
+ va.setDuration(30);
+ va.addUpdateListener(animation -> {
+ if (animation.getAnimatedFraction() == 1f) {
+ handler.post(() -> {
+ set.cancel();
+ endCalledRightAfterCancel[0] = vaListener.endCalled;
+ endCalledRightAfterCancel[1] = asListener.endCalled;
+ endLatch.countDown();
+ });
+ }
+ });
+ set.addListener(asListener);
+ va.addListener(vaListener);
+ set.play(va);
+
+ ValueAnimator.setPostNotifyEndListenerEnabled(true);
+ try {
+ handler.post(set::start);
+ assertTrue(endLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(endCalledRightAfterCancel[0]);
+ assertTrue(endCalledRightAfterCancel[1]);
+ } finally {
+ ValueAnimator.setPostNotifyEndListenerEnabled(false);
+ }
+ }
+
private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
final boolean[] value = new boolean[1];
PollingCheck.waitFor(() -> {
diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index 04698465e971..a55909f0c193 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -922,6 +922,36 @@ public class ValueAnimatorTests {
}
@Test
+ public void testCancelOnPendingEndListener() throws Throwable {
+ final CountDownLatch endLatch = new CountDownLatch(1);
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final boolean[] endCalledRightAfterCancel = new boolean[1];
+ final MyListener listener = new MyListener();
+ final ValueAnimator va = new ValueAnimator();
+ va.setFloatValues(0f, 1f);
+ va.setDuration(30);
+ va.addUpdateListener(animation -> {
+ if (animation.getAnimatedFraction() == 1f) {
+ handler.post(() -> {
+ va.cancel();
+ endCalledRightAfterCancel[0] = listener.endCalled;
+ endLatch.countDown();
+ });
+ }
+ });
+ va.addListener(listener);
+
+ ValueAnimator.setPostNotifyEndListenerEnabled(true);
+ try {
+ handler.post(va::start);
+ assertThat(endLatch.await(1, TimeUnit.SECONDS)).isTrue();
+ assertThat(endCalledRightAfterCancel[0]).isTrue();
+ } finally {
+ ValueAnimator.setPostNotifyEndListenerEnabled(false);
+ }
+ }
+
+ @Test
public void testZeroDuration() throws Throwable {
// Run two animators with zero duration, with one running forward and the other one
// backward. Check that the animations start and finish with the correct end fractions.
@@ -1182,6 +1212,7 @@ public class ValueAnimatorTests {
assertEquals(A1_START_VALUE, a1.getAnimatedValue());
});
}
+
class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener {
boolean wasRunning = false;
long firstRunningFrameTime = -1;
@@ -1207,7 +1238,7 @@ public class ValueAnimatorTests {
}
}
- class MyListener implements Animator.AnimatorListener {
+ static class MyListener implements Animator.AnimatorListener {
boolean startCalled = false;
boolean cancelCalled = false;
boolean endCalled = false;