Merge "Fix the multi-fingers gesture conflict with TouchExplorer" into rvc-dev

This commit is contained in:
Ryan Lin
2020-06-24 08:51:50 +00:00
committed by Android (Google) Code Review
3 changed files with 234 additions and 32 deletions

View File

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

View File

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

View File

@@ -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;
private MotionEvent pointerEvent(int action, float x, float 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;
}
}
}