Merge "Fix the multi-fingers gesture conflict with TouchExplorer" into rvc-dev
This commit is contained in:
@@ -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()");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user