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
This commit is contained in:
Ameer Armaly
2020-10-22 07:11:39 -07:00
parent 3b9c996eaa
commit 1cd4bd9ccf
5 changed files with 59 additions and 18 deletions

View File

@@ -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;

View File

@@ -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()) {

View File

@@ -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;
}

View File

@@ -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());

View File

@@ -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
*/