summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author ryanlwlin <ryanlwlin@google.com> 2020-06-22 21:29:04 +0800
committer ryanlwlin <ryanlwlin@google.com> 2020-06-24 13:46:05 +0800
commit2047cd4ba1feebe88ad5eb3f15eaf5f3b3bbe17d (patch)
treee2d64cf93d5d57852eda18ceaa05b12fcf626df4
parente658c76b5c1cdc67cf593ad000c5b862e31764fd (diff)
Fix the multi-fingers gesture conflict with TouchExplorer
TouchExplorer supports multi-finger gestures from R. However, FullScreenMagnificationGestureHandler has higher priority of receiving motion events than TouchExplorer. When the screen is zoomed in, two pointersdown gesture makes it transiting to PanningScalingState. To fix it, We post a tap tmeout to transit to PanningScalingState when receiving two pointers down. In this duration, any pointers action will make it transiting to DelegatingState. We also add a condition that is if the movement of any fingers exceeed the touchSlope, it will transit to PanningScalingState. Bug: 159508732 Test: atest FullScreenMagnificationGestureHandlerTest atest MagnificationGestureHandlerTest Change-Id: Ic72c0da68a6a4f1714da8d05f743d6218793a5da
-rw-r--r--services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java52
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java192
3 files changed, 234 insertions, 32 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
index afe6238ca38f..b7f8e674f3ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
@@ -26,6 +26,7 @@ import static android.view.MotionEvent.ACTION_UP;
import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logMagnificationTripleTap;
import static com.android.server.accessibility.gestures.GestureUtils.distance;
+import static com.android.server.accessibility.gestures.GestureUtils.distanceClosestPointerToPoint;
import static java.lang.Math.abs;
import static java.util.Arrays.asList;
@@ -37,6 +38,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.PointF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -615,6 +617,7 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
+ private static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3;
final int mLongTapMinDelay;
final int mSwipeMinDistance;
@@ -626,6 +629,7 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
private MotionEvent mPreLastDown;
private MotionEvent mLastUp;
private MotionEvent mPreLastUp;
+ private PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN);
private long mLastDetectingDownEventTime;
@@ -656,6 +660,10 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
transitionToDelegatingStateAndClear();
}
break;
+ case MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE: {
+ transitToPanningScalingStateAndClear();
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown message type: " + type);
}
@@ -702,14 +710,20 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
}
break;
case ACTION_POINTER_DOWN: {
- if (mMagnificationController.isMagnifying(mDisplayId)) {
- transitionTo(mPanningScalingState);
- clear();
+ if (mMagnificationController.isMagnifying(mDisplayId)
+ && event.getPointerCount() == 2) {
+ storeSecondPointerDownLocation(event);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
+ ViewConfiguration.getTapTimeout());
} else {
transitionToDelegatingStateAndClear();
}
}
break;
+ case ACTION_POINTER_UP: {
+ transitionToDelegatingStateAndClear();
+ }
+ break;
case ACTION_MOVE: {
if (isFingerDown()
&& distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
@@ -719,11 +733,19 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
// For convenience, viewport dragging takes precedence
// over insta-delegating on 3tap&swipe
// (which is a rare combo to be used aside from magnification)
- if (isMultiTapTriggered(2 /* taps */)) {
+ if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
transitionToViewportDraggingStateAndClear(event);
+ } else if (isMagnifying() && event.getPointerCount() == 2) {
+ //Primary pointer is swiping, so transit to PanningScalingState
+ transitToPanningScalingStateAndClear();
} else {
transitionToDelegatingStateAndClear();
}
+ } else if (isMagnifying() && secondPointerDownValid()
+ && distanceClosestPointerToPoint(
+ mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
+ //Second pointer is swiping, so transit to PanningScalingState
+ transitToPanningScalingStateAndClear();
}
}
break;
@@ -755,6 +777,21 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
}
}
+ private void storeSecondPointerDownLocation(MotionEvent event) {
+ final int index = event.getActionIndex();
+ mSecondPointerDownLocation.set(event.getX(index), event.getY(index));
+ }
+
+ private boolean secondPointerDownValid() {
+ return !(Float.isNaN(mSecondPointerDownLocation.x) && Float.isNaN(
+ mSecondPointerDownLocation.y));
+ }
+
+ private void transitToPanningScalingStateAndClear() {
+ transitionTo(mPanningScalingState);
+ clear();
+ }
+
public boolean isMultiTapTriggered(int numTaps) {
// Shortcut acts as the 2 initial taps
@@ -822,11 +859,13 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
setShortcutTriggered(false);
removePendingDelayedMessages();
clearDelayedMotionEvents();
+ mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
}
private void removePendingDelayedMessages() {
mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE);
}
private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
@@ -890,6 +929,7 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
transitionTo(mDelegatingState);
sendDelayedMotionEvents();
removePendingDelayedMessages();
+ mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
}
private void onTripleTap(MotionEvent up) {
@@ -907,6 +947,10 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler
}
}
+ private boolean isMagnifying() {
+ return mMagnificationController.isMagnifying(mDisplayId);
+ }
+
void transitionToViewportDraggingStateAndClear(MotionEvent down) {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
index ac6748089314..ec3041848356 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
@@ -1,5 +1,6 @@
package com.android.server.accessibility.gestures;
+import android.graphics.PointF;
import android.util.MathUtils;
import android.view.MotionEvent;
@@ -38,6 +39,27 @@ public final class GestureUtils {
return MathUtils.dist(first.getX(), first.getY(), second.getX(), second.getY());
}
+ /**
+ * Returns the minimum distance between {@code pointerDown} and each pointer of
+ * {@link MotionEvent}.
+ *
+ * @param pointerDown The action pointer location of the {@link MotionEvent} with
+ * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN}
+ * @param moveEvent The {@link MotionEvent} with {@link MotionEvent#ACTION_MOVE}
+ * @return the movement of the pointer.
+ */
+ public static double distanceClosestPointerToPoint(PointF pointerDown, MotionEvent moveEvent) {
+ float movement = Float.MAX_VALUE;
+ for (int i = 0; i < moveEvent.getPointerCount(); i++) {
+ final float moveDelta = MathUtils.dist(pointerDown.x, pointerDown.y, moveEvent.getX(i),
+ moveEvent.getY(i));
+ if (movement > moveDelta) {
+ movement = moveDelta;
+ }
+ }
+ return movement;
+ }
+
public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
return (deltaTime >= timeout);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
index 2007d4fff8c1..1cbee12720b0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
@@ -20,6 +20,7 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
import static com.android.server.testutils.TestUtils.strictMock;
@@ -38,11 +39,13 @@ import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
import android.util.DebugUtils;
import android.view.InputDevice;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -56,6 +59,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.IntConsumer;
/**
@@ -106,6 +112,7 @@ public class FullScreenMagnificationGestureHandlerTest {
// Co-prime x and y, to potentially catch x-y-swapped errors
public static final float DEFAULT_X = 301;
public static final float DEFAULT_Y = 299;
+ public static final PointF DEFAULT_POINT = new PointF(DEFAULT_X, DEFAULT_Y);
private static final int DISPLAY_0 = 0;
@@ -327,6 +334,107 @@ public class FullScreenMagnificationGestureHandlerTest {
});
}
+ @Test
+ public void testTwoFingersOneTap_zoomedState_dispatchMotionEvents() {
+ goFromStateIdleTo(STATE_ZOOMED);
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+ send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
+ send(upEvent());
+
+ assertIn(STATE_ZOOMED);
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_POINTER_UP));
+ expectedActions.add(Integer.valueOf(ACTION_UP));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+
+ returnToNormalFrom(STATE_ZOOMED);
+ }
+
+ @Test
+ public void testThreeFingersOneTap_zoomedState_dispatchMotionEvents() {
+ goFromStateIdleTo(STATE_ZOOMED);
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+ PointF pointer3 = new PointF(DEFAULT_X * 2, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2, pointer3}));
+ send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}));
+ send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}));
+ send(upEvent());
+
+ assertIn(STATE_ZOOMED);
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_POINTER_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_POINTER_UP));
+ expectedActions.add(Integer.valueOf(ACTION_POINTER_UP));
+ expectedActions.add(Integer.valueOf(ACTION_UP));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+
+ returnToNormalFrom(STATE_ZOOMED);
+ }
+
+ @Test
+ public void testFirstFingerSwipe_TwoPinterDownAndZoomedState_panningState() {
+ goFromStateIdleTo(STATE_ZOOMED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+ //The minimum movement to transit to panningState.
+ final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ pointer1.offset(sWipeMinDistance + 1, 0);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}));
+ assertIn(STATE_PANNING);
+
+ assertIn(STATE_PANNING);
+ returnToNormalFrom(STATE_PANNING);
+ }
+
+ @Test
+ public void testSecondFingerSwipe_TwoPinterDownAndZoomedState_panningState() {
+ goFromStateIdleTo(STATE_ZOOMED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+ //The minimum movement to transit to panningState.
+ final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ pointer2.offset(sWipeMinDistance + 1, 0);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}));
+ assertIn(STATE_PANNING);
+
+ assertIn(STATE_PANNING);
+ returnToNormalFrom(STATE_PANNING);
+ }
+
+ private void assertActionsInOrder(List<MotionEvent> actualEvents,
+ List<Integer> expectedActions) {
+ assertTrue(actualEvents.size() == expectedActions.size());
+ final int size = actualEvents.size();
+ for (int i = 0; i < size; i++) {
+ final int expectedAction = expectedActions.get(i);
+ final int actualAction = actualEvents.get(i).getActionMasked();
+ assertTrue(String.format(
+ "%dth action %s is not matched, actual events : %s, ", i,
+ MotionEvent.actionToString(expectedAction), actualEvents),
+ actualAction == expectedAction);
+ }
+ }
+
private void assertZoomsImmediatelyOnSwipeFrom(int state) {
goFromStateIdleTo(state);
swipeAndHold();
@@ -467,6 +575,7 @@ public class FullScreenMagnificationGestureHandlerTest {
goFromStateIdleTo(STATE_ZOOMED);
send(downEvent());
send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+ fastForward(ViewConfiguration.getTapTimeout());
} break;
case STATE_SCALING_AND_PANNING: {
goFromStateIdleTo(STATE_PANNING);
@@ -619,40 +728,67 @@ public class FullScreenMagnificationGestureHandlerTest {
MotionEvent.ACTION_UP, x, y, 0));
}
+
private MotionEvent pointerEvent(int action, float x, float y) {
- MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
- defPointerProperties.id = 0;
- defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
- MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
- pointerProperties.id = 1;
- pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
-
- MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
- defPointerCoords.x = DEFAULT_X;
- defPointerCoords.y = DEFAULT_Y;
- MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
- pointerCoords.x = x;
- pointerCoords.y = y;
+ return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)});
+ }
+
+ private MotionEvent pointerEvent(int action, PointF[] pointersPosition) {
+ final MotionEvent.PointerProperties[] PointerPropertiesArray =
+ new MotionEvent.PointerProperties[pointersPosition.length];
+ for (int i = 0; i < pointersPosition.length; i++) {
+ MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+ pointerProperties.id = i;
+ pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ PointerPropertiesArray[i] = pointerProperties;
+ }
+
+ final MotionEvent.PointerCoords[] pointerCoordsArray =
+ new MotionEvent.PointerCoords[pointersPosition.length];
+ for (int i = 0; i < pointersPosition.length; i++) {
+ MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+ pointerCoords.x = pointersPosition[i].x;
+ pointerCoords.y = pointersPosition[i].y;
+ pointerCoordsArray[i] = pointerCoords;
+ }
return MotionEvent.obtain(
- /* downTime */ mClock.now(),
- /* eventTime */ mClock.now(),
- /* action */ action,
- /* pointerCount */ 2,
- /* pointerProperties */ new MotionEvent.PointerProperties[] {
- defPointerProperties, pointerProperties},
- /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords },
- /* metaState */ 0,
- /* buttonState */ 0,
- /* xPrecision */ 1.0f,
- /* yPrecision */ 1.0f,
- /* deviceId */ 0,
- /* edgeFlags */ 0,
- /* source */ InputDevice.SOURCE_TOUCHSCREEN,
- /* flags */ 0);
+ /* downTime */ mClock.now(),
+ /* eventTime */ mClock.now(),
+ /* action */ action,
+ /* pointerCount */ pointersPosition.length,
+ /* pointerProperties */ PointerPropertiesArray,
+ /* pointerCoords */ pointerCoordsArray,
+ /* metaState */ 0,
+ /* buttonState */ 0,
+ /* xPrecision */ 1.0f,
+ /* yPrecision */ 1.0f,
+ /* deviceId */ 0,
+ /* edgeFlags */ 0,
+ /* source */ InputDevice.SOURCE_TOUCHSCREEN,
+ /* flags */ 0);
}
+
private String stateDump() {
return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages();
}
+
+ private class EventCaptor implements EventStreamTransformation {
+ List<MotionEvent> mEvents = new ArrayList<>();
+
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mEvents.add(event.copy());
+ }
+
+ @Override
+ public void setNext(EventStreamTransformation next) {
+ }
+
+ @Override
+ public EventStreamTransformation getNext() {
+ return null;
+ }
+ }
}