diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 2c38dc330a622..25f4abe2cb146 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -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; diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index e37225264af93..b3977829d1c34 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -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()) { diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index 7a39bc29e8e5e..dae16b227c5af 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -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; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java index 538e2d51e88f7..0e787853f617b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java @@ -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()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 0e32ea6ce057c..89bd625c8289f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -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 */