Loading core/java/android/view/ViewGroup.java +4 −3 Original line number Diff line number Diff line Loading @@ -2654,9 +2654,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager ViewRootImpl viewRootImpl = getViewRootImpl(); if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; final boolean isDispatchingBack = (viewRootImpl != null && viewRootImpl.getOnBackInvokedDispatcher().isDispatching()); if (!disallowIntercept || isDispatchingBack) { // Allow back to intercept touch final boolean isBackGestureInProgress = (viewRootImpl != null && viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress()); if (!disallowIntercept || isBackGestureInProgress) { // Allow back to intercept touch intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { Loading core/java/android/view/ViewRootImpl.java +1 −1 Original line number Diff line number Diff line Loading @@ -7228,7 +7228,7 @@ public final class ViewRootImpl implements ViewParent, private int doOnBackKeyEvent(KeyEvent keyEvent) { WindowOnBackInvokedDispatcher dispatcher = getOnBackInvokedDispatcher(); OnBackInvokedCallback topCallback = dispatcher.getTopCallback(); if (dispatcher.isDispatching()) { if (dispatcher.isBackGestureInProgress()) { return FINISH_NOT_HANDLED; } if (topCallback instanceof OnBackAnimationCallback) { Loading core/java/android/window/WindowOnBackInvokedDispatcher.java +12 −12 Original line number Diff line number Diff line Loading @@ -114,7 +114,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Updates the dispatcher state on a new {@link MotionEvent}. */ public void onMotionEvent(MotionEvent ev) { if (!isDispatching() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) { if (!isBackGestureInProgress() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) { return; } mTouchTracker.update(ev.getX(), ev.getY(), Float.NaN, Float.NaN); Loading Loading @@ -246,9 +246,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } /** * Indicates if the dispatcher is actively dispatching to a callback. * Indicates if a user gesture is currently in progress. */ public boolean isDispatching() { public boolean isBackGestureInProgress() { synchronized (mLock) { return mTouchTracker.isActive() || mImeDispatchingActive; } Loading Loading @@ -475,12 +475,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public void onBackStarted(BackMotionEvent backEvent) { mHandler.post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); // reset progress animator before dispatching onBackStarted to callback. This // ensures that onBackCancelled (of a previous gesture) is always dispatched // before onBackStarted if (callback != null) mProgressAnimator.reset(); mTouchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE); mTouchTracker.setShouldUpdateStartLocation(true); mTouchTracker.setGestureStartLocation( backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge()); final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { callback.onBackStarted(new BackEvent( backEvent.getTouchX(), Loading @@ -499,14 +504,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void onBackCancelled() { mHandler.post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback == null) { mTouchTracker.reset(); return; } mProgressAnimator.onBackCancelled(() -> { mTouchTracker.reset(); callback.onBackCancelled(); }); if (callback == null) return; mProgressAnimator.onBackCancelled(callback::onBackCancelled); }); } Loading core/java/com/android/internal/policy/DecorView.java +2 −2 Original line number Diff line number Diff line Loading @@ -523,8 +523,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl != null) { viewRootImpl.getOnBackInvokedDispatcher().onMotionEvent(event); // Intercept touch if back dispatching is active. if (viewRootImpl.getOnBackInvokedDispatcher().isDispatching()) { // Intercept touch if back gesture is in progress. if (viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress()) { return true; } } Loading core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +32 −3 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; Loading Loading @@ -371,6 +372,34 @@ public class WindowOnBackInvokedDispatcherTest { verify(mCallback1, never()).onBackCancelled(); } @Test public void onBackCancelled_calledBeforeOnBackStartedOfNewGesture() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); clearInvocations(mCallback1); callbackInfo.getCallback().onBackCancelled(); waitForIdle(); // verify onBackCancelled not yet called (since BackProgressAnimator animates // progress to 0 first) verify(mCallback1, never()).onBackCancelled(); // simulate start of new gesture while cancel animation is still running callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); // verify that onBackCancelled is called before onBackStarted InOrder orderVerifier = Mockito.inOrder(mCallback1); orderVerifier.verify(mCallback1).onBackCancelled(); orderVerifier.verify(mCallback1).onBackStarted(any(BackEvent.class)); } @Test public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); Loading Loading @@ -399,11 +428,11 @@ public class WindowOnBackInvokedDispatcherTest { callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); assertTrue(mDispatcher.isDispatching()); assertTrue(mDispatcher.isBackGestureInProgress()); callbackInfo.getCallback().onBackInvoked(); waitForIdle(); assertFalse(mDispatcher.isDispatching()); assertFalse(mDispatcher.isBackGestureInProgress()); } @Test Loading @@ -418,7 +447,7 @@ public class WindowOnBackInvokedDispatcherTest { callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); assertTrue(mDispatcher.isDispatching()); assertTrue(mDispatcher.isBackGestureInProgress()); assertTrue(mDispatcher.mTouchTracker.isActive()); main.runWithScissors(() -> mDispatcher.onMotionEvent(mMotionEvent), 100); Loading Loading
core/java/android/view/ViewGroup.java +4 −3 Original line number Diff line number Diff line Loading @@ -2654,9 +2654,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager ViewRootImpl viewRootImpl = getViewRootImpl(); if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; final boolean isDispatchingBack = (viewRootImpl != null && viewRootImpl.getOnBackInvokedDispatcher().isDispatching()); if (!disallowIntercept || isDispatchingBack) { // Allow back to intercept touch final boolean isBackGestureInProgress = (viewRootImpl != null && viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress()); if (!disallowIntercept || isBackGestureInProgress) { // Allow back to intercept touch intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { Loading
core/java/android/view/ViewRootImpl.java +1 −1 Original line number Diff line number Diff line Loading @@ -7228,7 +7228,7 @@ public final class ViewRootImpl implements ViewParent, private int doOnBackKeyEvent(KeyEvent keyEvent) { WindowOnBackInvokedDispatcher dispatcher = getOnBackInvokedDispatcher(); OnBackInvokedCallback topCallback = dispatcher.getTopCallback(); if (dispatcher.isDispatching()) { if (dispatcher.isBackGestureInProgress()) { return FINISH_NOT_HANDLED; } if (topCallback instanceof OnBackAnimationCallback) { Loading
core/java/android/window/WindowOnBackInvokedDispatcher.java +12 −12 Original line number Diff line number Diff line Loading @@ -114,7 +114,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Updates the dispatcher state on a new {@link MotionEvent}. */ public void onMotionEvent(MotionEvent ev) { if (!isDispatching() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) { if (!isBackGestureInProgress() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) { return; } mTouchTracker.update(ev.getX(), ev.getY(), Float.NaN, Float.NaN); Loading Loading @@ -246,9 +246,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } /** * Indicates if the dispatcher is actively dispatching to a callback. * Indicates if a user gesture is currently in progress. */ public boolean isDispatching() { public boolean isBackGestureInProgress() { synchronized (mLock) { return mTouchTracker.isActive() || mImeDispatchingActive; } Loading Loading @@ -475,12 +475,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public void onBackStarted(BackMotionEvent backEvent) { mHandler.post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); // reset progress animator before dispatching onBackStarted to callback. This // ensures that onBackCancelled (of a previous gesture) is always dispatched // before onBackStarted if (callback != null) mProgressAnimator.reset(); mTouchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE); mTouchTracker.setShouldUpdateStartLocation(true); mTouchTracker.setGestureStartLocation( backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge()); final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { callback.onBackStarted(new BackEvent( backEvent.getTouchX(), Loading @@ -499,14 +504,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void onBackCancelled() { mHandler.post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback == null) { mTouchTracker.reset(); return; } mProgressAnimator.onBackCancelled(() -> { mTouchTracker.reset(); callback.onBackCancelled(); }); if (callback == null) return; mProgressAnimator.onBackCancelled(callback::onBackCancelled); }); } Loading
core/java/com/android/internal/policy/DecorView.java +2 −2 Original line number Diff line number Diff line Loading @@ -523,8 +523,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl != null) { viewRootImpl.getOnBackInvokedDispatcher().onMotionEvent(event); // Intercept touch if back dispatching is active. if (viewRootImpl.getOnBackInvokedDispatcher().isDispatching()) { // Intercept touch if back gesture is in progress. if (viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress()) { return true; } } Loading
core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +32 −3 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; Loading Loading @@ -371,6 +372,34 @@ public class WindowOnBackInvokedDispatcherTest { verify(mCallback1, never()).onBackCancelled(); } @Test public void onBackCancelled_calledBeforeOnBackStartedOfNewGesture() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); clearInvocations(mCallback1); callbackInfo.getCallback().onBackCancelled(); waitForIdle(); // verify onBackCancelled not yet called (since BackProgressAnimator animates // progress to 0 first) verify(mCallback1, never()).onBackCancelled(); // simulate start of new gesture while cancel animation is still running callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); // verify that onBackCancelled is called before onBackStarted InOrder orderVerifier = Mockito.inOrder(mCallback1); orderVerifier.verify(mCallback1).onBackCancelled(); orderVerifier.verify(mCallback1).onBackStarted(any(BackEvent.class)); } @Test public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); Loading Loading @@ -399,11 +428,11 @@ public class WindowOnBackInvokedDispatcherTest { callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); assertTrue(mDispatcher.isDispatching()); assertTrue(mDispatcher.isBackGestureInProgress()); callbackInfo.getCallback().onBackInvoked(); waitForIdle(); assertFalse(mDispatcher.isDispatching()); assertFalse(mDispatcher.isBackGestureInProgress()); } @Test Loading @@ -418,7 +447,7 @@ public class WindowOnBackInvokedDispatcherTest { callbackInfo.getCallback().onBackStarted(mBackEvent); waitForIdle(); assertTrue(mDispatcher.isDispatching()); assertTrue(mDispatcher.isBackGestureInProgress()); assertTrue(mDispatcher.mTouchTracker.isActive()); main.runWithScissors(() -> mDispatcher.onMotionEvent(mMotionEvent), 100); Loading