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

Commit 0099ca8f authored by Arthur Hung's avatar Arthur Hung
Browse files

Ignore back invoke when window focus has lost

The back invoke target could lost focus during back navigation.
To prevent the non-focused window could still trigger back action cause
some unexpected behavior, this CL will listen focus changed and skip the
back invoke call.

Test: launch a trampoline activity and trigger back before next activity
shown.
Test: atest WindowOnBackInvokedDispatcherTest
Bug: 238050065

Change-Id: Ifd345b2283c5d07628e2884db6e4e13f3ec31e83
parent e2137ed5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -3759,6 +3759,7 @@ public final class ViewRootImpl implements ViewParent,
            }
        }
        mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
        mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus);

        // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
        // is lost, so we don't need to to force a flush - there might be other events such as
+3 −1
Original line number Diff line number Diff line
@@ -81,8 +81,10 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
            @OnBackInvokedDispatcher.Priority int priority,
            @NonNull OnBackInvokedCallback callback) {
        final Bundle bundle = new Bundle();
        // Always invoke back for ime without checking the window focus.
        final IOnBackInvokedCallback iCallback =
                new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
                new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback,
                        () -> true);
        bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
        bundle.putInt(RESULT_KEY_PRIORITY, priority);
        bundle.putInt(RESULT_KEY_ID, callback.hashCode());
+22 −4
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Supplier;

/**
 * Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -64,6 +65,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
    private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
            mOnBackInvokedCallbacks = new TreeMap<>();
    private final Checker mChecker;
    private boolean mHasFocus;

    public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
        mChecker = new Checker(applicationCallBackEnabled);
@@ -189,7 +191,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
                                    .ImeOnBackInvokedCallback
                                ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
                                        callback).getIOnBackInvokedCallback()
                                : new OnBackInvokedCallbackWrapper(callback);
                                : new OnBackInvokedCallbackWrapper(callback, this::hasFocus);
                callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
            }
            mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
@@ -198,6 +200,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
        }
    }

    /**
     * Called when window focus changed.
     */
    public void onWindowFocusChanged(boolean hasFocus) {
        mHasFocus = hasFocus;
    }

    private boolean hasFocus() {
        return mHasFocus;
    }

    public OnBackInvokedCallback getTopCallback() {
        if (mAllCallbacks.isEmpty()) {
            return null;
@@ -221,9 +234,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {

    static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
        private final WeakReference<OnBackInvokedCallback> mCallback;

        OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
        private final Supplier<Boolean> mHasFocus;
        OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback,
                @NonNull Supplier<Boolean> hasFocus) {
            mCallback = new WeakReference<>(callback);
            mHasFocus = hasFocus;
        }

        @Override
@@ -263,7 +278,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
                if (callback == null) {
                    return;
                }

                if (!mHasFocus.get()) {
                    Log.w(TAG, "Skip back invoke due to current focus has lost.");
                    return;
                }
                callback.onBackInvoked();
            });
        }
+28 −0
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ public class WindowOnBackInvokedDispatcherTest {
        MockitoAnnotations.initMocks(this);
        mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
        mDispatcher.attachToWindow(mWindowSession, mWindow);
        mDispatcher.onWindowFocusChanged(true);
    }

    private void waitForIdle() {
@@ -152,4 +153,31 @@ public class WindowOnBackInvokedDispatcherTest {
        waitForIdle();
        verify(mCallback2).onBackStarted();
    }

    @Test
    public void skipBackInvokeWhenNoFocus() throws RemoteException {
        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);

        mDispatcher.registerOnBackInvokedCallback(
                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);

        verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo(
                Mockito.eq(mWindow),
                captor.capture());

        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());

        // Should invoke back if it's still in focused.
        captor.getValue().getCallback().onBackInvoked();
        waitForIdle();
        verify(mCallback1).onBackInvoked();

        // In case the focus has lost during back gesture.
        mDispatcher.onWindowFocusChanged(false);

        captor.getValue().getCallback().onBackInvoked();
        waitForIdle();
        verifyZeroInteractions(mCallback1);
    }
}