Loading core/java/android/view/ViewRootImpl.java +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading core/java/android/window/ImeOnBackInvokedDispatcher.java +3 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading core/java/android/window/WindowOnBackInvokedDispatcher.java +22 −4 Original line number Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); }); } Loading core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +28 −0 Original line number Diff line number Diff line Loading @@ -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() { Loading Loading @@ -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); } } Loading
core/java/android/view/ViewRootImpl.java +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
core/java/android/window/ImeOnBackInvokedDispatcher.java +3 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading
core/java/android/window/WindowOnBackInvokedDispatcher.java +22 −4 Original line number Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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; Loading @@ -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 Loading Loading @@ -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(); }); } Loading
core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +28 −0 Original line number Diff line number Diff line Loading @@ -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() { Loading Loading @@ -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); } }