Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6e14231 authored by wilsonshih's avatar wilsonshih
Browse files

Fixes Activity cannot receive ACTION_CANCEL after onBackStarted.

Upon the client-side receiving onBackStarted, isBackGestureInProgress
returns true, and subsequent motion events are consumed by DecorView.
Once the CANCEL event is handled, the gesture is marked as intercepted,
and subsequent motion events are ignored.

Flag: com.android.window.flags.intercept_motion_from_move_to_cancel
Bug: 404173501
Test: atest OnBackInvokedCallbackGestureTest
Test: manual test with sample app, verify the CANCEL event should be
dispatched to the target which receive DOWN.

Change-Id: I8654a9d277ee38bbc483b6190332abb8833bc510
parent 2a2f062d
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -19,8 +19,10 @@ package android.view;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static android.view.flags.Flags.FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API;
import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
import static android.view.flags.Flags.scrollCaptureTargetZOrderFix;
import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;

import static com.android.window.flags.Flags.interceptMotionFromMoveToCancel;

import android.animation.LayoutTransition;
import android.annotation.CallSuper;
@@ -88,7 +90,6 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * <p>
 * A <code>ViewGroup</code> is a special view that can contain other views
@@ -2674,7 +2675,8 @@ 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 isBackGestureInProgress = (viewRootImpl != null
                final boolean isBackGestureInProgress = !interceptMotionFromMoveToCancel()
                        && (viewRootImpl != null
                        && viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress());
                if (!disallowIntercept || isBackGestureInProgress) {
                    // Allow back to intercept touch
+16 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ public class BackTouchTracker {
    private int mSwipeEdge;
    private boolean mShouldUpdateStartLocation = false;
    private TouchTrackerState mState = TouchTrackerState.INITIAL;
    private boolean mIsInterceptedMotionEvent;

    /**
     * Updates the tracker with a new motion event.
@@ -117,6 +118,20 @@ public class BackTouchTracker {
        return mState == TouchTrackerState.FINISHED;
    }

    /**
     * Returns whether current app should not receive motion event.
     */
    public boolean isInterceptedMotionEvent() {
        return mIsInterceptedMotionEvent;
    }

    /**
     * Marks the app will not receive motion event from current gesture.
     */
    public void setMotionEventIntercepted() {
        mIsInterceptedMotionEvent = true;
    }

    /** Sets the start location of the back gesture. */
    public void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
        mInitTouchX = touchX;
@@ -154,6 +169,7 @@ public class BackTouchTracker {
        mState = TouchTrackerState.INITIAL;
        mSwipeEdge = BackEvent.EDGE_LEFT;
        mShouldUpdateStartLocation = false;
        mIsInterceptedMotionEvent = false;
    }

    /** Creates a start {@link BackMotionEvent}. */
+18 −0
Original line number Diff line number Diff line
@@ -314,6 +314,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
        }
    }

    /**
     * Returns whether current app should not receive motion event.
     */
    public boolean isInterceptedMotionEvent() {
        synchronized (mLock) {
            return mTouchTracker.isInterceptedMotionEvent();
        }
    }

    /**
     * Marks the app will not receive motion event from current gesture.
     */
    public void setMotionEventIntercepted() {
        synchronized (mLock) {
            mTouchTracker.setMotionEventIntercepted();
        }
    }

    private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
        boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
        if (isInProgress && callback instanceof OnBackAnimationCallback) {
+11 −0
Original line number Diff line number Diff line
@@ -556,3 +556,14 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "intercept_motion_from_move_to_cancel"
    namespace: "windowing_frontend"
    description: "Ensure that the client receives ACTION_CANCEL when the back gesture is intercepted."
    bug: "404173501"
    is_fixed_read_only: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
 No newline at end of file
+58 −15
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_
import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;

import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
import static com.android.window.flags.Flags.interceptMotionFromMoveToCancel;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -428,11 +429,50 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (interceptBackProgress(ev)) {
            return true;
        }
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

    private boolean interceptBackProgress(MotionEvent ev) {
        if (!interceptMotionFromMoveToCancel()) {
            return false;
        }
        final ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl == null) {
            return false;
        }
        viewRootImpl.getOnBackInvokedDispatcher().onMotionEvent(ev);
        // Intercept touch if back gesture is in progress.
        boolean isBackGestureInProgress = viewRootImpl.getOnBackInvokedDispatcher()
                .isBackGestureInProgress();
        if (!isBackGestureInProgress && mWearGestureInterceptionDetector != null) {
            boolean wasIntercepting = mWearGestureInterceptionDetector.isIntercepting();
            boolean intercepting = mWearGestureInterceptionDetector.onInterceptTouchEvent(ev);
            if (wasIntercepting != intercepting) {
                viewRootImpl.updateDecorViewGestureInterception(intercepting);
            }
            if (intercepting) {
                isBackGestureInProgress = true;
            }
        }

        if (!isBackGestureInProgress) {
            return false;
        }
        // Intercept touch if back gesture is in progress.
        if (!viewRootImpl.getOnBackInvokedDispatcher().isInterceptedMotionEvent()) {
            viewRootImpl.getOnBackInvokedDispatcher().setMotionEventIntercepted();
            ev.setAction(MotionEvent.ACTION_CANCEL);
            // Return false to deliver the first CANCEL.
            return false;
        }
        return true;
    }

    @Override
    public boolean dispatchTrackballEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
@@ -511,6 +551,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
            }
        }

        if (!interceptMotionFromMoveToCancel()) {
            ViewRootImpl viewRootImpl = getViewRootImpl();
            if (viewRootImpl != null) {
                viewRootImpl.getOnBackInvokedDispatcher().onMotionEvent(event);
@@ -521,7 +562,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
            }
            if (viewRootImpl != null && mWearGestureInterceptionDetector != null) {
                boolean wasIntercepting = mWearGestureInterceptionDetector.isIntercepting();
            boolean intercepting = mWearGestureInterceptionDetector.onInterceptTouchEvent(event);
                boolean intercepting = mWearGestureInterceptionDetector
                        .onInterceptTouchEvent(event);
                if (wasIntercepting != intercepting) {
                    viewRootImpl.updateDecorViewGestureInterception(intercepting);
                }
@@ -529,6 +571,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
                    return true;
                }
            }
        }

        if (!SWEEP_OPEN_MENU) {
            return false;