Loading core/java/android/view/ViewRootImpl.java +1 −0 Original line number Original line Diff line number Diff line Loading @@ -3759,6 +3759,7 @@ public final class ViewRootImpl implements ViewParent, } } } } mFirstInputStage.onWindowFocusChanged(hasWindowFocus); mFirstInputStage.onWindowFocusChanged(hasWindowFocus); mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus); // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus // 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 // 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 Original line Diff line number Diff line Loading @@ -81,8 +81,10 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc @OnBackInvokedDispatcher.Priority int priority, @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { @NonNull OnBackInvokedCallback callback) { final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle(); // Always invoke back for ime without checking the window focus. final IOnBackInvokedCallback iCallback = final IOnBackInvokedCallback iCallback = new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback); new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback, () -> true); bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); bundle.putInt(RESULT_KEY_PRIORITY, priority); bundle.putInt(RESULT_KEY_PRIORITY, priority); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); Loading core/java/android/window/WindowOnBackInvokedDispatcher.java +22 −4 Original line number Original line Diff line number Diff line Loading @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; import java.util.Objects; import java.util.Objects; import java.util.TreeMap; import java.util.TreeMap; import java.util.function.Supplier; /** /** * Provides window based implementation of {@link OnBackInvokedDispatcher}. * Provides window based implementation of {@link OnBackInvokedDispatcher}. Loading Loading @@ -64,6 +65,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> mOnBackInvokedCallbacks = new TreeMap<>(); mOnBackInvokedCallbacks = new TreeMap<>(); private final Checker mChecker; private final Checker mChecker; private boolean mHasFocus; public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) { public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) { mChecker = new Checker(applicationCallBackEnabled); mChecker = new Checker(applicationCallBackEnabled); Loading Loading @@ -189,7 +191,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { .ImeOnBackInvokedCallback .ImeOnBackInvokedCallback ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) callback).getIOnBackInvokedCallback() callback).getIOnBackInvokedCallback() : new OnBackInvokedCallbackWrapper(callback); : new OnBackInvokedCallbackWrapper(callback, this::hasFocus); callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority); callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority); } } mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo); 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() { public OnBackInvokedCallback getTopCallback() { if (mAllCallbacks.isEmpty()) { if (mAllCallbacks.isEmpty()) { return null; return null; Loading @@ -221,9 +234,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private final WeakReference<OnBackInvokedCallback> mCallback; private final WeakReference<OnBackInvokedCallback> mCallback; private final Supplier<Boolean> mHasFocus; OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback, @NonNull Supplier<Boolean> hasFocus) { mCallback = new WeakReference<>(callback); mCallback = new WeakReference<>(callback); mHasFocus = hasFocus; } } @Override @Override Loading Loading @@ -263,7 +278,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { if (callback == null) { if (callback == null) { return; return; } } if (!mHasFocus.get()) { Log.w(TAG, "Skip back invoke due to current focus has lost."); return; } callback.onBackInvoked(); callback.onBackInvoked(); }); }); } } Loading core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +28 −0 Original line number Original line Diff line number Diff line Loading @@ -66,6 +66,7 @@ public class WindowOnBackInvokedDispatcherTest { MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this); mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */); mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */); mDispatcher.attachToWindow(mWindowSession, mWindow); mDispatcher.attachToWindow(mWindowSession, mWindow); mDispatcher.onWindowFocusChanged(true); } } private void waitForIdle() { private void waitForIdle() { Loading Loading @@ -152,4 +153,31 @@ public class WindowOnBackInvokedDispatcherTest { waitForIdle(); waitForIdle(); verify(mCallback2).onBackStarted(); 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 Original line Diff line number Diff line Loading @@ -3759,6 +3759,7 @@ public final class ViewRootImpl implements ViewParent, } } } } mFirstInputStage.onWindowFocusChanged(hasWindowFocus); mFirstInputStage.onWindowFocusChanged(hasWindowFocus); mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus); // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus // 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 // 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 Original line Diff line number Diff line Loading @@ -81,8 +81,10 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc @OnBackInvokedDispatcher.Priority int priority, @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { @NonNull OnBackInvokedCallback callback) { final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle(); // Always invoke back for ime without checking the window focus. final IOnBackInvokedCallback iCallback = final IOnBackInvokedCallback iCallback = new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback); new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback, () -> true); bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); bundle.putInt(RESULT_KEY_PRIORITY, priority); bundle.putInt(RESULT_KEY_PRIORITY, priority); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); Loading
core/java/android/window/WindowOnBackInvokedDispatcher.java +22 −4 Original line number Original line Diff line number Diff line Loading @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; import java.util.Objects; import java.util.Objects; import java.util.TreeMap; import java.util.TreeMap; import java.util.function.Supplier; /** /** * Provides window based implementation of {@link OnBackInvokedDispatcher}. * Provides window based implementation of {@link OnBackInvokedDispatcher}. Loading Loading @@ -64,6 +65,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> mOnBackInvokedCallbacks = new TreeMap<>(); mOnBackInvokedCallbacks = new TreeMap<>(); private final Checker mChecker; private final Checker mChecker; private boolean mHasFocus; public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) { public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) { mChecker = new Checker(applicationCallBackEnabled); mChecker = new Checker(applicationCallBackEnabled); Loading Loading @@ -189,7 +191,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { .ImeOnBackInvokedCallback .ImeOnBackInvokedCallback ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) callback).getIOnBackInvokedCallback() callback).getIOnBackInvokedCallback() : new OnBackInvokedCallbackWrapper(callback); : new OnBackInvokedCallbackWrapper(callback, this::hasFocus); callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority); callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority); } } mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo); 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() { public OnBackInvokedCallback getTopCallback() { if (mAllCallbacks.isEmpty()) { if (mAllCallbacks.isEmpty()) { return null; return null; Loading @@ -221,9 +234,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private final WeakReference<OnBackInvokedCallback> mCallback; private final WeakReference<OnBackInvokedCallback> mCallback; private final Supplier<Boolean> mHasFocus; OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback, @NonNull Supplier<Boolean> hasFocus) { mCallback = new WeakReference<>(callback); mCallback = new WeakReference<>(callback); mHasFocus = hasFocus; } } @Override @Override Loading Loading @@ -263,7 +278,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { if (callback == null) { if (callback == null) { return; return; } } if (!mHasFocus.get()) { Log.w(TAG, "Skip back invoke due to current focus has lost."); return; } callback.onBackInvoked(); callback.onBackInvoked(); }); }); } } Loading
core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +28 −0 Original line number Original line Diff line number Diff line Loading @@ -66,6 +66,7 @@ public class WindowOnBackInvokedDispatcherTest { MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this); mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */); mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */); mDispatcher.attachToWindow(mWindowSession, mWindow); mDispatcher.attachToWindow(mWindowSession, mWindow); mDispatcher.onWindowFocusChanged(true); } } private void waitForIdle() { private void waitForIdle() { Loading Loading @@ -152,4 +153,31 @@ public class WindowOnBackInvokedDispatcherTest { waitForIdle(); waitForIdle(); verify(mCallback2).onBackStarted(); 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); } } }