Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d9703505 authored by Riddle Hsu's avatar Riddle Hsu Committed by Android (Google) Code Review
Browse files

Merge "Run pending animation end callback immediately on cancel" into main

parents 069649d8 57341678
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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();
+31 −2
Original line number Diff line number Diff line
@@ -81,6 +81,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
@@ -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");
        }
+7 −0
Original line number Diff line number Diff line
@@ -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();
        }
    }
+1 −0
Original line number Diff line number Diff line
@@ -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;
        }

+38 −0
Original line number Diff line number Diff line
@@ -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(() -> {
Loading