diff options
author | Ryan Lin <ryanlwlin@google.com> | 2020-06-24 08:51:50 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-06-24 08:51:50 +0000 |
commit | 18905eb623ee5b30c1538a0add0ee1aec33df4be (patch) | |
tree | 483ce9fa4fbc717a5bf1a696219bcaae4b593344 | |
parent | 71f139471c11200d478e4d6dafe9189851814cd8 (diff) | |
parent | 2047cd4ba1feebe88ad5eb3f15eaf5f3b3bbe17d (diff) |
Merge "Fix the multi-fingers gesture conflict with TouchExplorer" into rvc-dev
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; + } + } } |