Loading services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java +48 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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, Loading Loading @@ -890,6 +929,7 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler transitionTo(mDelegatingState); sendDelayedMotionEvents(); removePendingDelayedMessages(); mSecondPointerDownLocation.set(Float.NaN, Float.NaN); } private void onTripleTap(MotionEvent up) { Loading @@ -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()"); Loading services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java +22 −0 Original line number Diff line number Diff line package com.android.server.accessibility.gestures; import android.graphics.PointF; import android.util.MathUtils; import android.view.MotionEvent; Loading Loading @@ -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); Loading services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java +164 −28 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading @@ -619,29 +728,37 @@ 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; 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 = 1; pointerProperties.id = i; pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; PointerPropertiesArray[i] = pointerProperties; } MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); defPointerCoords.x = DEFAULT_X; defPointerCoords.y = DEFAULT_Y; 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 = x; pointerCoords.y = y; 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 }, /* pointerCount */ pointersPosition.length, /* pointerProperties */ PointerPropertiesArray, /* pointerCoords */ pointerCoordsArray, /* metaState */ 0, /* buttonState */ 0, /* xPrecision */ 1.0f, Loading @@ -652,7 +769,26 @@ public class FullScreenMagnificationGestureHandlerTest { /* 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; } } } Loading
services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java +48 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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, Loading Loading @@ -890,6 +929,7 @@ class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler transitionTo(mDelegatingState); sendDelayedMotionEvents(); removePendingDelayedMessages(); mSecondPointerDownLocation.set(Float.NaN, Float.NaN); } private void onTripleTap(MotionEvent up) { Loading @@ -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()"); Loading
services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java +22 −0 Original line number Diff line number Diff line package com.android.server.accessibility.gestures; import android.graphics.PointF; import android.util.MathUtils; import android.view.MotionEvent; Loading Loading @@ -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); Loading
services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java +164 −28 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading @@ -619,29 +728,37 @@ 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; 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 = 1; pointerProperties.id = i; pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; PointerPropertiesArray[i] = pointerProperties; } MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); defPointerCoords.x = DEFAULT_X; defPointerCoords.y = DEFAULT_Y; 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 = x; pointerCoords.y = y; 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 }, /* pointerCount */ pointersPosition.length, /* pointerProperties */ PointerPropertiesArray, /* pointerCoords */ pointerCoordsArray, /* metaState */ 0, /* buttonState */ 0, /* xPrecision */ 1.0f, Loading @@ -652,7 +769,26 @@ public class FullScreenMagnificationGestureHandlerTest { /* 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; } } }