diff options
| author | 2022-07-15 11:37:24 +0000 | |
|---|---|---|
| committer | 2022-07-20 04:26:45 +0000 | |
| commit | 0099ca8fab618dfeefe25cb4c2146b2831900ee5 (patch) | |
| tree | bb89171966c8bb3951da9c2f2831512f16980644 | |
| parent | e2137ed5b3549be1fe2beb623aba468fdc629f92 (diff) | |
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
4 files changed, 54 insertions, 5 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8ec32a6d6b5b..7be8dffdf023 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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 diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index 5924844aa3b2..f1a052b61c59 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -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()); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index d147524d3b3d..02c5ebcc184e 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -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(); }); } diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index f448cb3091e7..b5194f637395 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -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); + } } |