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

Commit 6c7e5619 authored by Jorim Jaggi's avatar Jorim Jaggi Committed by Winson Chung
Browse files

Fix possible race conditions when cancelling animations

Since the surface is being released by the SurfaceAnimator, it was
possible that SurfaceAnimationRunner was still applying surface
changes on a released surface.

Solve this by introducing a cancel-lock on which all surface
operations are synchronized on.

Bug: 64674361
Test: SurfaceAnimationRunnerTest
Change-Id: I06ee9e8270f492faa1cbfd84a09a68c9a1a09ade
parent a5e10572
Loading
Loading
Loading
Loading
+51 −26
Original line number Original line Diff line number Diff line
@@ -43,12 +43,19 @@ class SurfaceAnimationRunner {


    private final Object mLock = new Object();
    private final Object mLock = new Object();


    /**
     * Lock for cancelling animations. Must be acquired on it's own, or after acquiring
     * {@link #mLock}
     */
    private final Object mCancelLock = new Object();

    @VisibleForTesting
    @VisibleForTesting
    Choreographer mChoreographer;
    Choreographer mChoreographer;


    private final Runnable mApplyTransactionRunnable = this::applyTransaction;
    private final Runnable mApplyTransactionRunnable = this::applyTransaction;
    private final AnimationHandler mAnimationHandler;
    private final AnimationHandler mAnimationHandler;
    private final Transaction mFrameTransaction;
    private final Transaction mFrameTransaction;
    private final AnimatorFactory mAnimatorFactory;
    private boolean mApplyScheduled;
    private boolean mApplyScheduled;


    @GuardedBy("mLock")
    @GuardedBy("mLock")
@@ -57,15 +64,15 @@ class SurfaceAnimationRunner {


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    @VisibleForTesting
    @VisibleForTesting
    final ArrayMap<SurfaceControl, ValueAnimator> mRunningAnimations = new ArrayMap<>();
    final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();


    SurfaceAnimationRunner() {
    SurfaceAnimationRunner() {
        this(null /* callbackProvider */, new Transaction());
        this(null /* callbackProvider */, null /* animatorFactory */, new Transaction());
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
    SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
            Transaction frameTransaction) {
            AnimatorFactory animatorFactory, Transaction frameTransaction) {
        SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
        SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
                0 /* timeout */);
                0 /* timeout */);
        mFrameTransaction = frameTransaction;
        mFrameTransaction = frameTransaction;
@@ -73,6 +80,9 @@ class SurfaceAnimationRunner {
        mAnimationHandler.setProvider(callbackProvider != null
        mAnimationHandler.setProvider(callbackProvider != null
                ? callbackProvider
                ? callbackProvider
                : new SfVsyncFrameCallbackProvider(mChoreographer));
                : new SfVsyncFrameCallbackProvider(mChoreographer));
        mAnimatorFactory = animatorFactory != null
                ? animatorFactory
                : SfValueAnimator::new;
    }
    }


    void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
    void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
@@ -95,11 +105,14 @@ class SurfaceAnimationRunner {
                mPendingAnimations.remove(leash);
                mPendingAnimations.remove(leash);
                return;
                return;
            }
            }
            final ValueAnimator anim = mRunningAnimations.get(leash);
            final RunningAnimation anim = mRunningAnimations.get(leash);
            if (anim != null) {
            if (anim != null) {
                mRunningAnimations.remove(leash);
                mRunningAnimations.remove(leash);
                synchronized (mCancelLock) {
                    anim.mCancelled = true;
                }
                SurfaceAnimationThread.getHandler().post(() -> {
                SurfaceAnimationThread.getHandler().post(() -> {
                    anim.cancel();
                    anim.mAnim.cancel();
                    applyTransaction();
                    applyTransaction();
                });
                });
            }
            }
@@ -114,44 +127,47 @@ class SurfaceAnimationRunner {
    }
    }


    private void startAnimationLocked(RunningAnimation a) {
    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator result = new SfValueAnimator();
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();


        // Animation length is already expected to be scaled.
        // Animation length is already expected to be scaled.
        result.overrideDurationScale(1.0f);
        anim.overrideDurationScale(1.0f);
        result.setDuration(a.mAnimSpec.getDuration());
        anim.setDuration(a.mAnimSpec.getDuration());
        result.addUpdateListener(animation -> {
        anim.addUpdateListener(animation -> {
            applyTransformation(a, mFrameTransaction, result.getCurrentPlayTime());
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    applyTransformation(a, mFrameTransaction, anim.getCurrentPlayTime());
                }
            }


            // Transaction will be applied in the commit phase.
            // Transaction will be applied in the commit phase.
            scheduleApplyTransaction();
            scheduleApplyTransaction();
        });
        });
        result.addListener(new AnimatorListenerAdapter() {
        anim.addListener(new AnimatorListenerAdapter() {

            private boolean mCancelled;

            @Override
            @Override
            public void onAnimationStart(Animator animation) {
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        mFrameTransaction.show(a.mLeash);
                        mFrameTransaction.show(a.mLeash);
                    }
                    }

                }
            @Override
            public void onAnimationCancel(Animator animation) {
                mCancelled = true;
            }
            }


            @Override
            @Override
            public void onAnimationEnd(Animator animation) {
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                synchronized (mLock) {
                    mRunningAnimations.remove(a.mLeash);
                    mRunningAnimations.remove(a.mLeash);
                }
                    synchronized (mCancelLock) {
                if (!mCancelled) {
                        if (!a.mCancelled) {
                            // Post on other thread that we can push final state without jank.
                            // Post on other thread that we can push final state without jank.
                            AnimationThread.getHandler().post(a.mFinishCallback);
                            AnimationThread.getHandler().post(a.mFinishCallback);
                        }
                        }
                    }
                    }
                }
            }
        });
        });
        result.start();
        anim.start();
        mRunningAnimations.put(a.mLeash, result);
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);
    }
    }


    private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
    private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
@@ -181,6 +197,10 @@ class SurfaceAnimationRunner {
        final AnimationSpec mAnimSpec;
        final AnimationSpec mAnimSpec;
        final SurfaceControl mLeash;
        final SurfaceControl mLeash;
        final Runnable mFinishCallback;
        final Runnable mFinishCallback;
        ValueAnimator mAnim;

        @GuardedBy("mCancelLock")
        private boolean mCancelled;


        RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
        RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
            mAnimSpec = animSpec;
            mAnimSpec = animSpec;
@@ -189,6 +209,11 @@ class SurfaceAnimationRunner {
        }
        }
    }
    }


    @VisibleForTesting
    interface AnimatorFactory {
        ValueAnimator makeAnimator();
    }

    /**
    /**
     * Value animator that uses sf-vsync signal to tick.
     * Value animator that uses sf-vsync signal to tick.
     */
     */
+38 −6
Original line number Original line Diff line number Diff line
@@ -24,9 +24,13 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


import android.animation.AnimationHandler;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Point;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
@@ -40,6 +44,7 @@ import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.animation.TranslateAnimation;


import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.SurfaceAnimationRunner.AnimatorFactory;


import org.junit.Before;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Rule;
@@ -63,6 +68,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {


    @Mock SurfaceControl mMockSurface;
    @Mock SurfaceControl mMockSurface;
    @Mock Transaction mMockTransaction;
    @Mock Transaction mMockTransaction;
    @Mock AnimationSpec mMockAnimationSpec;
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();


    private SurfaceAnimationRunner mSurfaceAnimationRunner;
    private SurfaceAnimationRunner mSurfaceAnimationRunner;
@@ -72,7 +78,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
    public void setUp() throws Exception {
    public void setUp() throws Exception {
        super.setUp();
        super.setUp();
        mFinishCallbackLatch = new CountDownLatch(1);
        mFinishCallbackLatch = new CountDownLatch(1);
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */,
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
                mMockTransaction);
                mMockTransaction);
    }
    }


@@ -104,7 +110,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {


    @Test
    @Test
    public void testCancel_notStarted() throws Exception {
    public void testCancel_notStarted() throws Exception {
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(),
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
                mMockTransaction);
                mMockTransaction);
        mSurfaceAnimationRunner
        mSurfaceAnimationRunner
                .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
                .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
@@ -117,11 +123,10 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {


    @Test
    @Test
    public void testCancel_running() throws Exception {
    public void testCancel_running() throws Exception {
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(),
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
                mMockTransaction);
                mMockTransaction);
        mSurfaceAnimationRunner
        mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
                .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
                mMockTransaction, this::finishedCallback);
                this::finishedCallback);
        waitUntilNextFrame();
        waitUntilNextFrame();
        assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
        assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
        mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
        mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
@@ -130,6 +135,33 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
        assertFinishCallbackNotCalled();
        assertFinishCallbackNotCalled();
    }
    }


    @Test
    public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
        mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
            {
                setFloatValues(0f, 1f);
            }

            @Override
            public void addUpdateListener(AnimatorUpdateListener listener) {
                super.addUpdateListener(animation -> {
                    // Sneaky test cancels animation just before applying frame to simulate
                    // interleaving of multiple threads. Muahahaha
                    if (animation.getCurrentPlayTime() > 0) {
                        mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
                    }
                    listener.onAnimationUpdate(animation);
                });
            }
        }, mMockTransaction);
        when(mMockAnimationSpec.getDuration()).thenReturn(200L);
        mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
                this::finishedCallback);
        waitUntilNextFrame();
        assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
        verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
    }

    private void waitUntilNextFrame() throws Exception {
    private void waitUntilNextFrame() throws Exception {
        final CountDownLatch latch = new CountDownLatch(1);
        final CountDownLatch latch = new CountDownLatch(1);
        mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
        mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,