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

Commit 1cd4bd9c authored by Ameer Armaly's avatar Ameer Armaly
Browse files

Do not transition from gesture detection state after gesture cancelation.

The agreed-upon behavior has always been that after gesture cancelation, touch explorer would exist in essentially a dead state.
It should not be possible to execute a two-finger passthrough gesture for example after doing an invalid gesture.
This is likely why we see GESTURE_PASSTHROUGH in cases where we expect GESTURE_UNKNOWN.
Fix: 171421786
Test: manual
Change-Id: Idce921c5278dea174ec5dad5cecf046f12ab113d

Change-Id: If9aeb611417bba08edd9f5a11f3d107c0b477bb8
parent 3b9c996e
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -111,9 +111,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
    // Shared state information.
    private TouchState mState;

    GestureManifold(Context context, Listener listener, TouchState state) {
    GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
        mContext = context;
        mHandler = new Handler(context.getMainLooper());
        mHandler = handler;
        mListener = listener;
        mState = state;
        mMultiFingerGesturesEnabled = false;
+11 −3
Original line number Diff line number Diff line
@@ -199,7 +199,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
                new SendAccessibilityEventDelayed(
                        TYPE_TOUCH_INTERACTION_END, mDetermineUserIntentTimeout);
        if (detector == null) {
            mGestureDetector = new GestureManifold(context, this, mState);
            mGestureDetector = new GestureManifold(context, this, mState, mHandler);
        } else {
            mGestureDetector = detector;
        }
@@ -391,7 +391,6 @@ public class TouchExplorer extends BaseEventStreamTransformation
    public boolean onGestureStarted() {
        // We have to perform gesture detection, so
        // clear the current state and try to detect.
        mState.startGestureDetecting();
        mSendHoverEnterAndMoveDelayed.cancel();
        mSendHoverExitDelayed.cancel();
        mExitGestureDetectionModeDelayed.post();
@@ -1195,7 +1194,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
    }

    private boolean shouldPerformGestureDetection(MotionEvent event) {
        if (mState.isDelegating()) {
        if (mState.isDelegating() || mState.isDragging()) {
            return false;
        }
        if (event.getActionMasked() == ACTION_DOWN) {
@@ -1288,6 +1287,15 @@ public class TouchExplorer extends BaseEventStreamTransformation
        }

        public void run() {
            if (mReceivedPointerTracker.getReceivedPointerDownCount() > 1) {
                // Multi-finger touch exploration doesn't make sense.
                Slog.e(
                        LOG_TAG,
                        "Attempted touch exploration with "
                                + mReceivedPointerTracker.getReceivedPointerDownCount()
                                + " pointers down.");
                return;
            }
            // Send an accessibility event to announce the touch exploration start.
            mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
            if (isSendMotionEventsEnabled()) {
+0 −3
Original line number Diff line number Diff line
@@ -207,9 +207,6 @@ public class TouchState {
            case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
                startGestureDetecting();
                break;
            case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
                startTouchInteracting();
                break;
            default:
                break;
        }
+3 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.accessibilityservice.AccessibilityService;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.os.Handler;
import android.view.MotionEvent;

import androidx.test.InstrumentationRegistry;
@@ -56,7 +57,8 @@ public class GestureManifoldTest {
        // Construct a testable GestureManifold.
        mResultListener = mock(GestureManifold.Listener.class);
        mState = new TouchState();
        mManifold = new GestureManifold(context, mResultListener, mState);
        Handler handler = new Handler(context.getMainLooper());
        mManifold = new GestureManifold(context, mResultListener, mState, handler);
        // Play the role of touch explorer in updating the shared state.
        when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());

+43 −9
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_E

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import android.content.Context;
import android.graphics.PointF;
@@ -129,10 +128,9 @@ public class TouchExplorerTest {
        mContext = InstrumentationRegistry.getContext();
        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
        AccessibilityManagerService ams = new AccessibilityManagerService(mContext);
        GestureManifold detector = mock(GestureManifold.class);
        mCaptor = new EventCaptor();
        mHandler = new TestHandler();
        mTouchExplorer = new TouchExplorer(mContext, ams, detector, mHandler);
        mTouchExplorer = new TouchExplorer(mContext, ams, null, mHandler);
        mTouchExplorer.setNext(mCaptor);
    }

@@ -272,8 +270,8 @@ public class TouchExplorerTest {
        // Wait for the views responding to hover enter/move events.
        mHandler.fastForward(oneThirdUserIntentTimeout);
        // Simulate receiving the a11y exit event sent by the first view.
        AccessibilityEvent a11yExitEvent = AccessibilityEvent.obtain(
                AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
        AccessibilityEvent a11yExitEvent =
                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
        mTouchExplorer.onAccessibilityEvent(a11yExitEvent);

        // Wait for running the hover exit event runnable. After it, touch-exploration end event
@@ -364,6 +362,39 @@ public class TouchExplorerTest {
        assertCapturedEventsNoHistory();
    }

    @Test
    public void testCanceledGesture_shouldDoNothing() {
        mTouchExplorer.setMultiFingerGesturesEnabled(true);
        mTouchExplorer.setTwoFingerPassthroughEnabled(true);
        // Start a three-finger swipe.
        send(downEvent());
        send(pointerDownEvent());
        send(thirdPointerDownEvent());
        moveEachPointers(mLastEvent, p(0, 200), p(0, 200), p(0, 200));
        send(mLastEvent);
        assertState(STATE_GESTURE_DETECTING);
        mHandler.fastForward(2 * (int) Swipe.MAX_TIME_TO_CONTINUE_SWIPE_MS);
        // Lift the third finger but keep the other two going.
        send(thirdPointerUpEvent());
        // Manually construct the next move event. Using moveEachPointers() will batch the move
        // event onto the pointer up event which will mean that the move event still has a pointer
        // count of 3.
        // Todo: refactor to avoid using batching as there is no special reason to do it that way.
        float[] x = new float[2];
        float[] y = new float[2];
        x[0] = mLastEvent.getX(0) + 100;
        x[1] = mLastEvent.getX(1) + 100;
        y[0] = mLastEvent.getY(0) + 100;
        y[1] = mLastEvent.getY(1) + 100;
        send(manyPointerEvent(ACTION_MOVE, x, y));
        // Ensure that no two-finger passthrough is being executed.
        assertState(STATE_GESTURE_DETECTING);
        assertNoCapturedEvents();
        send(pointerUpEvent());
        send(upEvent());
        mTouchExplorer.setMultiFingerGesturesEnabled(false);
    }

    private static MotionEvent fromTouchscreen(MotionEvent ev) {
        ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        return ev;
@@ -414,8 +445,7 @@ public class TouchExplorerTest {
                    throw new IllegalArgumentException("Illegal state: " + state);
            }
        } catch (Throwable t) {
            throw new RuntimeException(
                    "Failed to go to state " + stateToString(state), t);
            throw new RuntimeException("Failed to go to state " + stateToString(state), t);
        }
    }

@@ -465,6 +495,10 @@ public class TouchExplorerTest {
                TouchState.getStateSymbolicName(mTouchExplorer.getState().getState()));
    }

    private void assertNoCapturedEvents() {
        assertEquals(0, getCapturedEvents().size());
    }

    private void assertCapturedEvents(int... actionsInOrder) {
        final int eventCount = actionsInOrder.length;
        assertEquals(eventCount, getCapturedEvents().size());
@@ -623,8 +657,8 @@ public class TouchExplorerTest {
    }

    /**
     * A {@link android.os.Handler} that doesn't process messages until {@link
     * #fastForward(int)} is invoked.
     * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
     * invoked.
     *
     * @see com.android.server.testutils.TestHandler
     */