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

Commit 6639646e authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "Remove finish-callbacks from BackProgressAnimator upon callback unregistration" into main

parents dcc2db21 1f6abece
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -212,6 +212,17 @@ public class BackProgressAnimator implements DynamicAnimation.OnAnimationUpdateL
        mBackCancelledFinishRunnable = null;
    }

    /**
     * Removes the finishCallback passed into {@link #onBackCancelled}
     */
    public void removeOnBackInvokedFinishCallback() {
        if (mBackInvokedFlingAnim != null) {
            mBackInvokedFlingAnim.removeUpdateListener(mOnBackInvokedFlingUpdateListener);
            mBackInvokedFlingAnim.removeEndListener(mOnAnimationEndListener);
        }
        mBackInvokedFinishRunnable = null;
    }

    /** Returns true if the back animation is in progress. */
    @VisibleForTesting(visibility = PACKAGE)
    public boolean isBackAnimationInProgress() {
+2 −0
Original line number Diff line number Diff line
@@ -243,6 +243,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
            if (previousTopCallback == callback) {
                // We should call onBackCancelled() when an active callback is removed from
                // dispatcher.
                mProgressAnimator.removeOnBackCancelledFinishCallback();
                mProgressAnimator.removeOnBackInvokedFinishCallback();
                sendCancelledIfInProgress(callback);
                mHandler.post(mProgressAnimator::reset);
                setTopOnBackInvokedCallback(getTopCallback());
+103 −0
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.view.IWindowSession;
import android.view.ImeBackAnimationController;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -61,6 +62,10 @@ import org.mockito.junit.MockitoRule;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Tests for {@link WindowOnBackInvokedDispatcherTest}
@@ -117,6 +122,8 @@ public class WindowOnBackInvokedDispatcherTest {

        mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper());
        mDispatcher.attachToWindow(mWindowSession, mWindow, null, mImeBackAnimationController);
        clearInvocations(mCallback1);
        clearInvocations(mCallback2);
    }

    private void waitForIdle() {
@@ -472,6 +479,102 @@ public class WindowOnBackInvokedDispatcherTest {
        verifyImeCallackRegistrations();
    }

    @Test
    public void onBackInvoked_notCalledAfterCallbackUnregistration()
            throws RemoteException, InterruptedException {
        // Setup a callback that unregisters itself after the gesture is finished but before the
        // fling animation has ended
        final AtomicBoolean unregisterOnProgressUpdate = new AtomicBoolean(false);
        final AtomicInteger onBackInvokedCalled = new AtomicInteger(0);
        final CountDownLatch onBackCancelledCalled = new CountDownLatch(1);
        OnBackAnimationCallback onBackAnimationCallback = new OnBackAnimationCallback() {
            @Override
            public void onBackProgressed(@NonNull BackEvent backEvent) {
                if (unregisterOnProgressUpdate.get()) {
                    mDispatcher.unregisterOnBackInvokedCallback(this);
                }
            }

            @Override
            public void onBackInvoked() {
                onBackInvokedCalled.getAndIncrement();
            }

            @Override
            public void onBackCancelled() {
                onBackCancelledCalled.countDown();
            }
        };
        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, onBackAnimationCallback);
        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();

        callbackInfo.getCallback().onBackStarted(mBackEvent);
        waitForIdle();
        assertTrue(mDispatcher.mProgressAnimator.isBackAnimationInProgress());

        // simulate back gesture finished and onBackInvoked() called, which starts the fling slow
        // down animation. By setting unregisterOnProgressUpdate to true, the callback will
        // unregister itself as soon as it receives the first progress event (coming from the
        // generated fling slow down events)
        unregisterOnProgressUpdate.set(true);
        callbackInfo.getCallback().onBackInvoked();
        waitForIdle();
        onBackCancelledCalled.await(1000, TimeUnit.MILLISECONDS);

        // verify that onBackCancelled is called in this case instead of onBackInvoked
        assertEquals(0, onBackCancelledCalled.getCount());
        assertEquals(0, onBackInvokedCalled.get());
        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
        assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
    }

    @Test
    public void onBackCancelled_calledOnceAfterCallbackUnregistration()
            throws RemoteException, InterruptedException {
        // Setup a callback that unregisters itself after the gesture is finished but before the
        // progress is animated back to 0f
        final AtomicBoolean unregisterOnProgressUpdate = new AtomicBoolean(false);
        final AtomicInteger onBackInvokedCalled = new AtomicInteger(0);
        final CountDownLatch onBackCancelledCalled = new CountDownLatch(1);
        OnBackAnimationCallback onBackAnimationCallback = new OnBackAnimationCallback() {
            @Override
            public void onBackProgressed(@NonNull BackEvent backEvent) {
                if (unregisterOnProgressUpdate.get()) {
                    mDispatcher.unregisterOnBackInvokedCallback(this);
                }
            }

            @Override
            public void onBackInvoked() {
                onBackInvokedCalled.getAndIncrement();
            }

            @Override
            public void onBackCancelled() {
                onBackCancelledCalled.countDown();
            }
        };
        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, onBackAnimationCallback);
        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();

        callbackInfo.getCallback().onBackStarted(mBackEvent);
        waitForIdle();
        assertTrue(mDispatcher.mProgressAnimator.isBackAnimationInProgress());

        // simulate back gesture finished and onBackCancelled() called, which starts the progress
        // animation back to 0f. On the first progress emission, the callback will unregister itself
        unregisterOnProgressUpdate.set(true);
        callbackInfo.getCallback().onBackCancelled();
        waitForIdle();
        onBackCancelledCalled.await(1000, TimeUnit.MILLISECONDS);

        // verify that onBackCancelled is called exactly once in this case
        assertEquals(0, onBackCancelledCalled.getCount());
        assertEquals(0, onBackInvokedCalled.get());
        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
        assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress());
    }

    private void verifyImeCallackRegistrations() throws RemoteException {
        // verify default callback is replaced with ImeBackAnimationController
        mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT);
+25 −0
Original line number Diff line number Diff line
@@ -177,6 +177,31 @@ public class BackProgressAnimatorTest {
        assertEquals(1, finishCallbackCalled.getCount());
    }

    @Test
    public void testOnBackInvokedFinishCallbackNotInvokedWhenRemoved() throws InterruptedException {
        // Give the animator some progress.
        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
        mMainThreadHandler.post(
                () -> mProgressAnimator.onBackProgressed(backEvent));
        mTargetProgressCalled.await(1, TimeUnit.SECONDS);
        assertNotNull(mReceivedBackEvent);

        // Trigger back invoked animation
        CountDownLatch finishCallbackCalled = new CountDownLatch(1);
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> mProgressAnimator.onBackInvoked(finishCallbackCalled::countDown));

        // remove onBackCancelled finishCallback (while progress is still animating to 0)
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> mProgressAnimator.removeOnBackInvokedFinishCallback());

        // call reset (which triggers the finishCallback invocation, if one is present)
        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset());

        // verify that finishCallback is not invoked
        assertEquals(1, finishCallbackCalled.getCount());
    }

    private void onGestureProgress(BackEvent backEvent) {
        if (mTargetProgress == backEvent.getProgress()) {
            mReceivedBackEvent = backEvent;