diff options
12 files changed, 357 insertions, 183 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java b/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java index feed18d438c7..3d8f5173d25a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java @@ -98,8 +98,7 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene } mProcessMotionEvent = true; for (int i = 0; i < mGestureMatchers.size(); i++) { - final GestureMatcher matcher = - mGestureMatchers.get(i); + final GestureMatcher matcher = mGestureMatchers.get(i); matcher.onMotionEvent(event, rawEvent, policyFlags); if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) { clear(); @@ -128,7 +127,10 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene MotionEvent rawEvent, int policyFlags) { if (state == GestureMatcher.STATE_GESTURE_COMPLETED) { mListener.onGestureCompleted(gestureId, event, rawEvent, policyFlags); - //Clear the states in onMotionEvent(). + // Ideally we clear the states in onMotionEvent(), this case is for hold gestures. + // If we clear before processing up event , then MultiTap matcher cancels the gesture + // due to incorrect state. It ends up listener#onGestureCancelled is called even + // the gesture is detected. if (!mProcessMotionEvent) { clear(); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java index 7a4d9e34b657..570e0ce5490d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java @@ -33,7 +33,7 @@ import java.lang.annotation.RetentionPolicy; class MagnificationGestureMatcher { private static final int GESTURE_BASE = 100; - public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1; + public static final int GESTURE_TWO_FINGERS_DOWN_OR_SWIPE = GESTURE_BASE + 1; public static final int GESTURE_SWIPE = GESTURE_BASE + 2; public static final int GESTURE_SINGLE_TAP = GESTURE_BASE + 3; public static final int GESTURE_SINGLE_TAP_AND_HOLD = GESTURE_BASE + 4; @@ -41,7 +41,7 @@ class MagnificationGestureMatcher { public static final int GESTURE_TRIPLE_TAP_AND_HOLD = GESTURE_BASE + 6; @IntDef(prefix = {"GESTURE_MAGNIFICATION_"}, value = { - GESTURE_TWO_FINGER_DOWN, + GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, GESTURE_SWIPE }) @Retention(RetentionPolicy.SOURCE) @@ -57,8 +57,8 @@ class MagnificationGestureMatcher { switch (gestureId) { case GESTURE_SWIPE: return "GESTURE_SWIPE"; - case GESTURE_TWO_FINGER_DOWN: - return "GESTURE_TWO_FINGER_DOWN"; + case GESTURE_TWO_FINGERS_DOWN_OR_SWIPE: + return "GESTURE_TWO_FINGERS_DOWN_OR_SWIPE"; case GESTURE_SINGLE_TAP: return "GESTURE_SINGLE_TAP"; case GESTURE_SINGLE_TAP_AND_HOLD: @@ -71,6 +71,12 @@ class MagnificationGestureMatcher { return "none"; } + /** + * @param context + * @return the duration in milliseconds between the first tap's down event and + * the second tap's down event to be considered that the user is going to performing + * panning/scaling gesture. + */ static int getMagnificationMultiTapTimeout(Context context) { return ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger( R.integer.config_screen_magnification_multi_tap_adjustment); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java index a209086ba475..085c343ff631 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java @@ -65,7 +65,7 @@ class MagnificationGesturesObserver implements GesturesObserver.Listener { * the last event before timeout. * * @see MagnificationGestureMatcher#GESTURE_SWIPE - * @see MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN + * @see MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE */ void onGestureCompleted(@GestureId int gestureId, long lastDownEventTime, List<MotionEventInfo> delayedEventQueue, MotionEvent event); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java b/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java index cd5061fa3163..fa15ac1c0e4f 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java @@ -49,6 +49,11 @@ class SimpleSwipe extends GestureMatcher { } @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelGesture(event, rawEvent, policyFlags); + } + + @Override protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (gestureMatched(event, rawEvent, policyFlags)) { completeGesture(event, rawEvent, policyFlags); @@ -65,7 +70,7 @@ class SimpleSwipe extends GestureMatcher { } private boolean gestureMatched(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - return mLastDown != null && (distance(mLastDown, event) >= mSwipeMinDistance); + return mLastDown != null && (distance(mLastDown, event) > mSwipeMinDistance); } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java deleted file mode 100644 index 173a5b82e003..000000000000 --- a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.accessibility.magnification; - -import android.content.Context; -import android.os.Handler; -import android.view.MotionEvent; - -import com.android.server.accessibility.gestures.GestureMatcher; - -/** - * - * This class is responsible for matching two fingers down gestures. The gesture matching - * result is determined in a duration. - */ -final class TwoFingersDown extends GestureMatcher { - - private MotionEvent mLastDown; - private final int mDetectionDurationMillis; - - TwoFingersDown(Context context) { - super(MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, - new Handler(context.getMainLooper()), null); - mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( - context); - } - - @Override - protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - mLastDown = MotionEvent.obtain(event); - cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags); - } - - @Override - protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mLastDown == null) { - cancelGesture(event, rawEvent, policyFlags); - } - completeGesture(event, rawEvent, policyFlags); - } - - @Override - protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - cancelGesture(event, rawEvent, policyFlags); - } - - @Override - public void clear() { - if (mLastDown != null) { - mLastDown.recycle(); - mLastDown = null; - } - super.clear(); - } - - @Override - protected String getGestureName() { - return this.getClass().getSimpleName(); - } -} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java new file mode 100644 index 000000000000..1742bd46d865 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Handler; +import android.util.MathUtils; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.server.accessibility.gestures.GestureMatcher; + +/** + * This class is responsible for detecting that the user is using two fingers to perform + * swiping gestures or just stay pressed on the screen. The gesture matching result is determined + * in a duration. + */ +final class TwoFingersDownOrSwipe extends GestureMatcher { + + private final int mDoubleTapTimeout; + private final int mDetectionDurationMillis; + private final int mSwipeMinDistance; + private MotionEvent mFirstPointerDown; + private MotionEvent mSecondPointerDown; + + TwoFingersDownOrSwipe(Context context) { + super(MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, + new Handler(context.getMainLooper()), null); + mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( + context); + mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); + mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop(); + + } + + @Override + protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + mFirstPointerDown = MotionEvent.obtain(event); + cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags); + } + + @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mFirstPointerDown == null) { + cancelGesture(event, rawEvent, policyFlags); + } + if (event.getPointerCount() == 2) { + mSecondPointerDown = MotionEvent.obtain(event); + completeAfter(mDoubleTapTimeout, event, rawEvent, policyFlags); + } else { + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mFirstPointerDown == null || mSecondPointerDown == null) { + return; + } + if (distance(mFirstPointerDown, /* move */ event) > mSwipeMinDistance) { + completeGesture(event, rawEvent, policyFlags); + return; + } + if (distance(mSecondPointerDown, /* move */ event) > mSwipeMinDistance) { + // The second pointer is swiping. + completeGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelGesture(event, rawEvent, policyFlags); + } + + @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelGesture(event, rawEvent, policyFlags); + } + + @Override + public void clear() { + if (mFirstPointerDown != null) { + mFirstPointerDown.recycle(); + mFirstPointerDown = null; + } + if (mSecondPointerDown != null) { + mSecondPointerDown.recycle(); + mSecondPointerDown = null; + } + super.clear(); + } + + @Override + protected String getGestureName() { + return this.getClass().getSimpleName(); + } + + private static double distance(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) { + final int downActionIndex = downEvent.getActionIndex(); + final int downPointerId = downEvent.getPointerId(downActionIndex); + final int moveActionIndex = moveEvent.findPointerIndex(downPointerId); + if (moveActionIndex < 0) { + return -1; + } + return MathUtils.dist(downEvent.getX(downActionIndex), downEvent.getY(downActionIndex), + moveEvent.getX(moveActionIndex), moveEvent.getY(moveActionIndex)); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 55a911eea821..fa3406217fa8 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -323,7 +323,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl * manipulate the window magnifier or want to interact with current UI. The rule of leaving * this state is as follows: * <ol> - * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN} is detected, + * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE} is detected, * {@link State} will be transited to {@link PanningScalingGestureState}.</li> * <li> If other gesture is detected and the last motion event is neither ACTION_UP nor * ACTION_CANCEL. @@ -357,7 +357,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl new SimpleSwipe(context), multiTap, multiTapAndHold, - new TwoFingersDown(context)); + new TwoFingersDownOrSwipe(context)); } @Override @@ -399,7 +399,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl Slog.d(mLogTag, "onGestureDetected : delayedEventQueue = " + delayedEventQueue); } - if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN + if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE && mWindowMagnificationMgr.pointersInWindow(mDisplayId, motionEvent) > 0) { transitionTo(mObservePanningScalingState); } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java index 895fb1757504..5dbf837b08b2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java @@ -69,7 +69,7 @@ public class MagnificationGesturesObserverTest { mContext = InstrumentationRegistry.getContext(); mInstrumentation = InstrumentationRegistry.getInstrumentation(); mObserver = new MagnificationGesturesObserver(mCallback, new SimpleSwipe(mContext), - new TwoFingersDown(mContext)); + new TwoFingersDownOrSwipe(mContext)); } @Test @@ -77,9 +77,7 @@ public class MagnificationGesturesObserverTest { final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY, DEFAULT_X , DEFAULT_Y); - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(moveEvent, moveEvent, 0); - }); + mObserver.onMotionEvent(moveEvent, moveEvent, 0); verify(mCallback).onGestureCancelled(eq(0L), mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(moveEvent))); @@ -92,9 +90,7 @@ public class MagnificationGesturesObserverTest { final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X , DEFAULT_Y); - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(downEvent, downEvent, 0); - }); + mObserver.onMotionEvent(downEvent, downEvent, 0); verify(mCallback).onGestureCancelled(eq(0L), mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent))); @@ -108,9 +104,7 @@ public class MagnificationGesturesObserverTest { final int timeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( mContext) + 100; - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(downEvent, downEvent, 0); - }); + mObserver.onMotionEvent(downEvent, downEvent, 0); verify(mCallback, timeout(timeoutMillis)).onGestureCancelled(eq(downEvent.getDownTime()), mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent))); @@ -121,14 +115,12 @@ public class MagnificationGesturesObserverTest { public void sendEventsOfSwiping_onGestureCompleted() { final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X, DEFAULT_Y); - final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY, DEFAULT_X + swipeDistance, DEFAULT_Y + swipeDistance); - mInstrumentation.runOnMainSync(() -> { - mObserver.onMotionEvent(downEvent, downEvent, 0); - mObserver.onMotionEvent(moveEvent, moveEvent, 0); - }); + mObserver.onMotionEvent(downEvent, downEvent, 0); + mObserver.onMotionEvent(moveEvent, moveEvent, 0); verify(mCallback).onGestureCompleted(eq(MagnificationGestureMatcher.GESTURE_SWIPE), eq(downEvent.getDownTime()), mEventInfoArgumentCaptor.capture(), diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java index 01631bf21a63..0ca631e4ed62 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java @@ -78,7 +78,7 @@ public class SimpleSwipeTest { @Test public void sendSwipeEvent_onGestureCompleted() { - final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1; final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X, DEFAULT_Y); final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java index ed8dc4e470de..162d2a9d98af 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,20 @@ package com.android.server.accessibility.magnification; +import static com.android.server.accessibility.utils.TouchEventGenerator.movePointer; +import static com.android.server.accessibility.utils.TouchEventGenerator.twoPointersDownEvents; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import android.content.Context; +import android.graphics.PointF; import android.view.Display; import android.view.MotionEvent; +import android.view.ViewConfiguration; import androidx.test.InstrumentationRegistry; @@ -35,18 +41,20 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** - * Tests for {@link TwoFingersDown}. + * Tests for {@link TwoFingersDownOrSwipe}. */ -public class TwoFingersDownTest { +public class TwoFingersDownOrSwipeTest { private static final float DEFAULT_X = 100f; private static final float DEFAULT_Y = 100f; - private static Context sContext; + private static float sSwipeMinDistance; private static int sTimeoutMillis; + private static Context sContext; - private Context mContext; private GesturesObserver mGesturesObserver; @Mock private GesturesObserver.Listener mListener; @@ -56,13 +64,13 @@ public class TwoFingersDownTest { sContext = InstrumentationRegistry.getContext(); sTimeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout( sContext) + 100; + sSwipeMinDistance = ViewConfiguration.get(sContext).getScaledTouchSlop() + 1; } @Before public void setUp() { - mContext = InstrumentationRegistry.getContext(); MockitoAnnotations.initMocks(this); - mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDown(mContext)); + mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDownOrSwipe(sContext)); } @Test @@ -78,24 +86,16 @@ public class TwoFingersDownTest { @Test public void sendTwoFingerDownEvent_onGestureCompleted() { - final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, - DEFAULT_X, DEFAULT_Y); - final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); - defPointerCoords.x = DEFAULT_X; - defPointerCoords.y = DEFAULT_Y; - final MotionEvent.PointerCoords secondPointerCoords = new MotionEvent.PointerCoords(); - secondPointerCoords.x = DEFAULT_X + 10; - secondPointerCoords.y = DEFAULT_Y + 10; + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); - final MotionEvent twoPointersDownEvent = TouchEventGenerator.twoPointersDownEvent( - Display.DEFAULT_DISPLAY, defPointerCoords, secondPointerCoords); - - mGesturesObserver.onMotionEvent(downEvent, downEvent, 0); - mGesturesObserver.onMotionEvent(twoPointersDownEvent, twoPointersDownEvent, 0); + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted( - MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, twoPointersDownEvent, - twoPointersDownEvent, 0); + MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, downEvents.get(1), + downEvents.get(1), 0); } @Test @@ -108,7 +108,39 @@ public class TwoFingersDownTest { mGesturesObserver.onMotionEvent(downEvent, downEvent, 0); mGesturesObserver.onMotionEvent(upEvent, upEvent, 0); - verify(mListener, timeout(sTimeoutMillis)).onGestureCancelled(any(MotionEvent.class), - any(MotionEvent.class), eq(0)); + verify(mListener, after(ViewConfiguration.getDoubleTapTimeout())).onGestureCancelled( + any(MotionEvent.class), any(MotionEvent.class), eq(0)); + } + + @Test + public void firstPointerMove_twoPointersDown_onGestureCompleted() { + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } + final MotionEvent moveEvent = movePointer(downEvents.get(1), 0, sSwipeMinDistance, 0); + + mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0); + + verify(mListener).onGestureCompleted( + MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent, + moveEvent, 0); + } + + @Test + public void secondPointerMove_twoPointersDown_onGestureCompleted() { + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } + final MotionEvent moveEvent = movePointer(downEvents.get(1), 1, sSwipeMinDistance, 0); + + mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0); + + verify(mListener).onGestureCompleted( + MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent, + moveEvent, 0); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index 4b7ebbc29b46..b9498d641ed7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -24,13 +24,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; +import android.graphics.PointF; import android.graphics.Rect; import android.os.RemoteException; import android.util.DebugUtils; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.ViewConfiguration; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.EventStreamTransformation; @@ -43,6 +45,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.function.IntConsumer; /** @@ -75,7 +78,7 @@ public class WindowMagnificationGestureHandlerTest { @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getContext(); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0, mock(WindowMagnificationManager.Callback.class)); mMockConnection = new MockWindowMagnificationConnection(); @@ -100,8 +103,8 @@ public class WindowMagnificationGestureHandlerTest { * Covers following paths to get to and back between each state and {@link #STATE_IDLE}. * <p> * <br> IDLE -> SHOW_MAGNIFIER [label="a11y\nbtn"] - * <br> SHOW_MAGNIFIER -> TWO_FINGER_DOWN [label="2hold"] - * <br> TWO_FINGER_DOWN -> SHOW_MAGNIFIER [label="release"] + * <br> SHOW_MAGNIFIER -> TWO_FINGERS_DOWN [label="2hold"] + * <br> TWO_FINGERS_DOWN -> SHOW_MAGNIFIER [label="release"] * <br> SHOW_MAGNIFIER -> IDLE [label="a11y\nbtn"] * <br> IDLE -> SHOW_MAGNIFIER_TRIPLE_TAP [label="3tap"] * <br> SHOW_MAGNIFIER_TRIPLE_TAP -> IDLE [label="3tap"] @@ -112,18 +115,16 @@ public class WindowMagnificationGestureHandlerTest { */ @Test public void testEachState_isReachableAndRecoverable() { - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - forEachState(state -> { - goFromStateIdleTo(state); - assertIn(state); - returnToNormalFrom(state); - try { - assertIn(STATE_IDLE); - } catch (AssertionError e) { - throw new AssertionError("Failed while testing state " + stateToString(state), - e); - } - }); + forEachState(state -> { + goFromStateIdleTo(state); + assertIn(state); + returnToNormalFrom(state); + try { + assertIn(STATE_IDLE); + } catch (AssertionError e) { + throw new AssertionError("Failed while testing state " + stateToString(state), + e); + } }); } @@ -209,10 +210,19 @@ public class WindowMagnificationGestureHandlerTest { case STATE_TWO_FINGERS_DOWN: { goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT); final Rect frame = mMockConnection.getMirrorWindowFrame(); - send(downEvent(frame.centerX(), frame.centerY())); - //Second finger is outside the window. - send(twoPointerDownEvent(new float[]{frame.centerX(), frame.centerX() + 10}, - new float[]{frame.centerY(), frame.centerY() + 10})); + final PointF firstPointerDown = new PointF(frame.centerX(), frame.centerY()); + // The second finger is outside the window. + final PointF secondPointerDown = new PointF(frame.right + 10, + frame.bottom + 10); + final List<MotionEvent> motionEvents = + TouchEventGenerator.twoPointersDownEvents(DISPLAY_0, + firstPointerDown, secondPointerDown); + for (MotionEvent downEvent: motionEvents) { + send(downEvent); + } + // Wait for two-finger down gesture completed. + Thread.sleep(ViewConfiguration.getDoubleTapTimeout()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } break; case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: { @@ -301,16 +311,6 @@ public class WindowMagnificationGestureHandlerTest { send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); } - private MotionEvent twoPointerDownEvent(float[] x, float[] y) { - final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); - defPointerCoords.x = x[0]; - defPointerCoords.y = y[0]; - final MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); - pointerCoords.x = x[1]; - pointerCoords.y = y[1]; - return TouchEventGenerator.twoPointersDownEvent(DISPLAY_0, defPointerCoords, pointerCoords); - } - private String stateDump() { return "\nCurrent state dump:\n" + mWindowMagnificationGestureHandler.mCurrentState; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java index a05881f78892..fbcde533aa9f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java @@ -19,16 +19,19 @@ package com.android.server.accessibility.utils; 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 android.view.MotionEvent.PointerCoords; +import android.graphics.PointF; import android.os.SystemClock; import android.view.InputDevice; import android.view.MotionEvent; +import java.util.ArrayList; +import java.util.List; + /** * generates {@link MotionEvent} with source {@link InputDevice#SOURCE_TOUCHSCREEN} - * */ public class TouchEventGenerator { @@ -39,44 +42,68 @@ public class TouchEventGenerator { public static MotionEvent moveEvent(int displayId, float x, float y) { return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y); } + public static MotionEvent upEvent(int displayId, float x, float y) { return generateSingleTouchEvent(displayId, ACTION_UP, x, y); } - public static MotionEvent twoPointersDownEvent(int displayId, PointerCoords defPointerCoords, - PointerCoords pointerCoords) { - return generateTwoPointersEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords, - pointerCoords); - } - private static MotionEvent generateSingleTouchEvent(int displayId, int action, float x, float y) { - final long downTime = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(downTime, downTime, - action, x, y, 0); - ev.setDisplayId(displayId); - ev.setSource(InputDevice.SOURCE_TOUCHSCREEN); - return ev; + return generateMultiplePointersEvent(displayId, action, new PointF(x, y)); } - private static MotionEvent generateTwoPointersEvent(int displayId, int action, - PointerCoords defPointerCoords, PointerCoords pointerCoords) { - final long downTime = SystemClock.uptimeMillis(); - 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; + /** + * Creates a list of {@link MotionEvent} with given pointers location. + * + * @param displayId the display id + * @param pointF1 location on the screen of the second pointer. + * @param pointF2 location on the screen of the second pointer. + * @return a list of {@link MotionEvent} with {@link MotionEvent#ACTION_DOWN} and {@link + * MotionEvent#ACTION_POINTER_DOWN}. + */ + public static List<MotionEvent> twoPointersDownEvents(int displayId, PointF pointF1, + PointF pointF2) { + final List<MotionEvent> downEvents = new ArrayList<>(); + final MotionEvent downEvent = generateMultiplePointersEvent(displayId, + MotionEvent.ACTION_DOWN, pointF1); + downEvents.add(downEvent); + final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int action = ACTION_POINTER_DOWN | actionIndex; + + final MotionEvent twoPointersDownEvent = generateMultiplePointersEvent(displayId, action, + pointF1, pointF2); + downEvents.add(twoPointersDownEvent); + return downEvents; + } + + private static MotionEvent generateMultiplePointersEvent(int displayId, int action, + PointF... pointFs) { + final int length = pointFs.length; + final MotionEvent.PointerCoords[] pointerCoordsArray = + new MotionEvent.PointerCoords[length]; + final MotionEvent.PointerProperties[] pointerPropertiesArray = + new MotionEvent.PointerProperties[length]; + for (int i = 0; i < length; i++) { + MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); + pointerCoords.x = pointFs[i].x; + pointerCoords.y = pointFs[i].y; + pointerCoordsArray[i] = pointerCoords; + + MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties(); + pointerProperties.id = i; + pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; + pointerPropertiesArray[i] = pointerProperties; + } + + final long downTime = SystemClock.uptimeMillis(); final MotionEvent ev = MotionEvent.obtain( /* downTime */ downTime, /* eventTime */ downTime, /* action */ action, - /* pointerCount */ 2, - /* pointerProperties */ new MotionEvent.PointerProperties[] { - defPointerProperties, pointerProperties}, - /* pointerCoords */ new PointerCoords[] { defPointerCoords, pointerCoords }, + /* pointerCount */ length, + /* pointerProperties */ pointerPropertiesArray, + /* pointerCoords */ pointerCoordsArray, /* metaState */ 0, /* buttonState */ 0, /* xPrecision */ 1.0f, @@ -88,4 +115,65 @@ public class TouchEventGenerator { ev.setDisplayId(displayId); return ev; } + + /** + * Generates a move event that moves the pointer of the original event with given index. + * The original event should not be up event and we don't support + * {@link MotionEvent#ACTION_POINTER_UP} now. + * + * @param originalEvent the move or down event + * @param pointerIndex the index of the pointer we want to move. + * @param offsetX the offset in X coordinate. + * @param offsetY the offset in Y coordinate. + * @return a motion event with move action. + */ + public static MotionEvent movePointer(MotionEvent originalEvent, int pointerIndex, + float offsetX, float offsetY) { + if (originalEvent.getActionMasked() == ACTION_UP) { + throw new IllegalArgumentException("No pointer is on the screen"); + } + + if (originalEvent.getActionMasked() == ACTION_POINTER_UP) { + throw new IllegalArgumentException("unsupported yet,please implement it first"); + } + + final int pointerCount = originalEvent.getPointerCount(); + if (pointerIndex >= pointerCount) { + throw new IllegalArgumentException( + pointerIndex + "is not available with pointer count" + pointerCount); + } + final int action = MotionEvent.ACTION_MOVE; + final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[pointerCount]; + for (int i = 0; i < pointerCount; i++) { + MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties(); + originalEvent.getPointerProperties(i, pointerProperty); + pp[i] = pointerProperty; + } + + final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[pointerCount]; + for (int i = 0; i < pointerCount; i++) { + MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords(); + originalEvent.getPointerCoords(i, pointerCoord); + pc[i] = pointerCoord; + } + pc[pointerIndex].x += offsetX; + pc[pointerIndex].y += offsetY; + final MotionEvent ev = MotionEvent.obtain( + /* downTime */ originalEvent.getDownTime(), + /* eventTime */ SystemClock.uptimeMillis(), + /* action */ action, + /* pointerCount */ 2, + /* pointerProperties */ pp, + /* pointerCoords */ pc, + /* metaState */ 0, + /* buttonState */ 0, + /* xPrecision */ 1.0f, + /* yPrecision */ 1.0f, + /* deviceId */ 0, + /* edgeFlags */ 0, + /* source */ originalEvent.getSource(), + /* flags */ originalEvent.getFlags()); + ev.setDisplayId(originalEvent.getDisplayId()); + return ev; + } } |