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

Commit 75b025d1 authored by omarmt's avatar omarmt
Browse files

Cancel the back animation if the topCallback is removed or the...

Cancel the back animation if the topCallback is removed or the WindowOnBackInvokedDispatcher is detached from the window.

We'll send an onBackCancelled() event whenever the top callback is in progress and has been removed from the callbacks.

Changes:
- We send onBackCancelled() and reset the ProgressAnimator when clear() is called.
- Added a check to avoid sending an onBackInvoked() to the app if the ProgressAnimator is not in progress.

Test: atest WindowOnBackInvokedDispatcherTest
Bug: 255324784
Change-Id: I0dcc1fb869070dd5bab6c0802c933783266594e5
parent 4c45ee8c
Loading
Loading
Loading
Loading
+31 −9
Original line number Diff line number Diff line
@@ -163,16 +163,22 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
        // Re-populate the top callback to WM if the removed callback was previously the top one.
        if (previousTopCallback == callback) {
            // We should call onBackCancelled() when an active callback is removed from dispatcher.
            if (mProgressAnimator.isBackAnimationInProgress()
                    && callback instanceof OnBackAnimationCallback) {
                // The ProgressAnimator will handle the new topCallback, so we don't want to call
                // onBackCancelled() on it. We call immediately the callback instead.
            sendCancelledIfInProgress(callback);
            setTopOnBackInvokedCallback(getTopCallback());
        }
    }

    private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
        boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
        if (isInProgress && callback instanceof OnBackAnimationCallback) {
            OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
            animatedCallback.onBackCancelled();
                Log.d(TAG, "The callback was removed while a back animation was in progress, "
                        + "an onBackCancelled() was dispatched.");
            if (DEBUG) {
                Log.d(TAG, "sendCancelIfRunning: callback canceled");
            }
            setTopOnBackInvokedCallback(getTopCallback());
        } else {
            Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
                    + "callback=" + callback);
        }
    }

@@ -188,9 +194,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
            mImeDispatcher = null;
        }
        if (!mAllCallbacks.isEmpty()) {
            OnBackInvokedCallback topCallback = getTopCallback();
            if (topCallback != null) {
                sendCancelledIfInProgress(topCallback);
            } else {
                // Should not be possible
                Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
            }
            // Clear binder references in WM.
            setTopOnBackInvokedCallback(null);
        }

        // We should also stop running animations since all callbacks have been removed.
        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
        Handler.getMain().post(mProgressAnimator::reset);
        mAllCallbacks.clear();
        mOnBackInvokedCallbacks.clear();
    }
@@ -342,12 +359,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
        @Override
        public void onBackInvoked() throws RemoteException {
            Handler.getMain().post(() -> {
                boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
                mProgressAnimator.reset();
                final OnBackInvokedCallback callback = mCallbackRef.get();
                if (callback == null) {
                    Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference.");
                    return;
                }
                if (callback instanceof OnBackAnimationCallback && !isInProgress) {
                    Log.w(TAG, "ProgressAnimator was not in progress, skip onBackInvoked().");
                    return;
                }
                callback.onBackInvoked();
            });
        }
+39 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -340,4 +341,42 @@ public class WindowOnBackInvokedDispatcherTest {
        verify(mCallback1).onBackCancelled();
        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
    }

    @Test
    public void onBackInvoked_calledAfterOnBackStarted() throws RemoteException {
        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();

        callbackInfo.getCallback().onBackStarted(mBackEvent);

        waitForIdle();
        verify(mCallback1).onBackStarted(any(BackEvent.class));

        callbackInfo.getCallback().onBackInvoked();

        waitForIdle();
        verify(mCallback1).onBackInvoked();
        verify(mCallback1, never()).onBackCancelled();
    }

    @Test
    public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);

        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();

        callbackInfo.getCallback().onBackStarted(mBackEvent);

        waitForIdle();
        verify(mCallback1).onBackStarted(any(BackEvent.class));

        // This should trigger mCallback1.onBackCancelled()
        mDispatcher.detachFromWindow();
        // This should be ignored by mCallback1
        callbackInfo.getCallback().onBackInvoked();

        waitForIdle();
        verify(mCallback1, never()).onBackInvoked();
        verify(mCallback1).onBackCancelled();
    }
}