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
This commit is contained in:
Arthur Hung
2022-07-15 11:37:24 +00:00
parent e2137ed5b3
commit 0099ca8fab
4 changed files with 54 additions and 5 deletions

View File

@@ -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

View File

@@ -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());

View File

@@ -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();
});
}

View File

@@ -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);
}
}