diff options
| author | 2023-11-08 04:04:38 +0000 | |
|---|---|---|
| committer | 2023-11-08 04:04:38 +0000 | |
| commit | 79d91aa990ba6d2c39233449dd1fc0e8b209a84e (patch) | |
| tree | d5f4e6402d25ccbc2e32eea80e1dad4ef0588e45 | |
| parent | f6235a2397f3565739b5e8bec2c5f2593b3cca5a (diff) | |
| parent | 767587b577200de01de72919743353dd62d625aa (diff) | |
Merge "feat(MultiFingerMultiTap): Implement two finger triple tap feature on WindowMagnificationGestureHandler" into main
5 files changed, 162 insertions, 17 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java index 46b46288cf84..9c6dfc61cb3b 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java @@ -31,7 +31,7 @@ import java.util.Arrays; * This class matches multi-finger multi-tap gestures. The number of fingers and the number of taps * for each instance is specified in the constructor. */ -class MultiFingerMultiTap extends GestureMatcher { +public class MultiFingerMultiTap extends GestureMatcher { // The target number of taps. final int mTargetTapCount; @@ -56,7 +56,7 @@ class MultiFingerMultiTap extends GestureMatcher { * @throws IllegalArgumentException if <code>fingers<code/> is less than 2 * or <code>taps<code/> is not positive. */ - MultiFingerMultiTap( + public MultiFingerMultiTap( Context context, int fingers, int taps, diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java index 9c541008f33f..f586036f62a0 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java @@ -23,9 +23,9 @@ import android.view.MotionEvent; * This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers * and taps for each instance is specified in the constructor. */ -class MultiFingerMultiTapAndHold extends MultiFingerMultiTap { +public class MultiFingerMultiTapAndHold extends MultiFingerMultiTap { - MultiFingerMultiTapAndHold( + public MultiFingerMultiTapAndHold( Context context, int fingers, int taps, 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 5953d0d309de..36e751181cfd 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -41,6 +41,8 @@ import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.accessibility.Flags; import com.android.server.accessibility.gestures.GestureMatcher; +import com.android.server.accessibility.gestures.MultiFingerMultiTap; +import com.android.server.accessibility.gestures.MultiFingerMultiTapAndHold; import com.android.server.accessibility.gestures.MultiTap; import com.android.server.accessibility.gestures.MultiTapAndHold; @@ -476,6 +478,15 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl null)); mGestureMatchers.add(new TwoFingersDownOrSwipe(context)); + if (mDetectTwoFingerTripleTap) { + mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2, + /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP, + null)); + mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2, + /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD, + null)); + } + mGesturesObserver = new MagnificationGesturesObserver(this, mGestureMatchers.toArray(new GestureMatcher[mGestureMatchers.size()])); } else { @@ -512,7 +523,9 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public boolean shouldStopDetection(MotionEvent motionEvent) { return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId) - && !mDetectSingleFingerTripleTap; + && !mDetectSingleFingerTripleTap + && !(mDetectTwoFingerTripleTap + && Flags.enableMagnificationMultipleFingerMultipleTapGesture()); } @Override 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 c88d6e45218f..612a091a6b1b 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 @@ -29,6 +29,9 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.TestableContext; import android.util.DebugUtils; import android.view.InputDevice; @@ -40,6 +43,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.utils.TouchEventGenerator; import org.junit.After; @@ -59,20 +63,29 @@ import java.util.function.IntConsumer; @RunWith(AndroidJUnit4.class) public class WindowMagnificationGestureHandlerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public static final int STATE_IDLE = 1; public static final int STATE_SHOW_MAGNIFIER_SHORTCUT = 2; public static final int STATE_TWO_FINGERS_DOWN = 3; public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4; public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5; public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6; + public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7; + public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8; + public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9; //TODO: Test it after can injecting Handler to GestureMatcher is available. public static final int FIRST_STATE = STATE_IDLE; public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD; + public static final int LAST_STATE_WITH_MULTI_FINGER = + STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD; // Co-prime x and y, to potentially catch x-y-swapped errors public static final float DEFAULT_TAP_X = 301; public static final float DEFAULT_TAP_Y = 299; + public static final PointF DEFAULT_POINT = new PointF(DEFAULT_TAP_X, DEFAULT_TAP_Y); private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY; @Rule @@ -141,7 +154,22 @@ public class WindowMagnificationGestureHandlerTest { throw new AssertionError("Failed while testing state " + stateToString(state), e); } - }); + }, LAST_STATE); + } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testEachState_enabledMultiFinger_isReachableAndRecoverable() { + 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); + } + }, LAST_STATE_WITH_MULTI_FINGER); } @Test @@ -159,8 +187,29 @@ public class WindowMagnificationGestureHandlerTest { returnToNormalFrom(state1); } } - }); - }); + }, LAST_STATE); + }, LAST_STATE); + } + + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testStates_enabledMultiFinger_areMutuallyExclusive() { + forEachState(state1 -> { + forEachState(state2 -> { + if (state1 < state2) { + goFromStateIdleTo(state1); + try { + assertIn(state2); + fail("State " + stateToString(state1) + " also implies state " + + stateToString(state2) + stateDump()); + } catch (AssertionError e) { + // expected + returnToNormalFrom(state1); + } + } + }, LAST_STATE_WITH_MULTI_FINGER); + }, LAST_STATE_WITH_MULTI_FINGER); } @Test @@ -187,8 +236,8 @@ public class WindowMagnificationGestureHandlerTest { returnToNormalFrom(STATE_SHOW_MAGNIFIER_TRIPLE_TAP); } - private void forEachState(IntConsumer action) { - for (int state = FIRST_STATE; state <= LAST_STATE; state++) { + private void forEachState(IntConsumer action, int lastState) { + for (int state = FIRST_STATE; state <= lastState; state++) { action.accept(state); } } @@ -207,14 +256,16 @@ public class WindowMagnificationGestureHandlerTest { } break; case STATE_SHOW_MAGNIFIER_SHORTCUT: - case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: { + case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: + case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState == mWindowMagnificationGestureHandler.mDetectingState, state); - } break; case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: { + case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState == mWindowMagnificationGestureHandler.mViewportDraggingState, state); @@ -286,6 +337,29 @@ public class WindowMagnificationGestureHandlerTest { tapAndHold(); } break; + case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: { + twoFingerTap(); + twoFingerTap(); + twoFingerTap(); + // Wait for two-finger tap gesture completed. + SystemClock.sleep(ViewConfiguration.getDoubleTapMinTime() + 500); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + break; + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { + twoFingerTap(); + twoFingerTap(); + twoFingerTapAndHold(); + } + break; + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { + // enabled then perform two finger triple tap and hold gesture + goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT); + twoFingerTap(); + twoFingerTap(); + twoFingerTapAndHold(); + } + break; default: throw new IllegalArgumentException("Illegal state: " + state); } @@ -319,13 +393,22 @@ public class WindowMagnificationGestureHandlerTest { tap(); } break; - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: { + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); - } - break; - case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: { + break; + case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT); + break; + case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: { + twoFingerTap(); + twoFingerTap(); + twoFingerTap(); + // Wait for two-finger tap gesture completed. + SystemClock.sleep(ViewConfiguration.getDoubleTapMinTime() + 500); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } break; default: @@ -365,6 +448,16 @@ public class WindowMagnificationGestureHandlerTest { return TouchEventGenerator.downEvent(DISPLAY_0, x, y); } + private MotionEvent pointerDownEvent(float x, float y) { + return TouchEventGenerator.pointerDownEvent(DISPLAY_0, + new PointF[] {DEFAULT_POINT, new PointF(x, y)}); + } + + private MotionEvent pointerUpEvent(float x, float y) { + return TouchEventGenerator.pointerUpEvent(DISPLAY_0, + new PointF[] {DEFAULT_POINT, new PointF(x, y)}); + } + private MotionEvent upEvent(float x, float y) { return TouchEventGenerator.upEvent(DISPLAY_0, x, y); } @@ -379,6 +472,19 @@ public class WindowMagnificationGestureHandlerTest { SystemClock.sleep(ViewConfiguration.getLongPressTimeout() + 100); } + private void twoFingerTap() { + send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); + send(pointerDownEvent(DEFAULT_TAP_X * 2, DEFAULT_TAP_Y)); + send(pointerUpEvent(DEFAULT_TAP_X * 2, DEFAULT_TAP_Y)); + send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); + } + + private void twoFingerTapAndHold() { + send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); + send(pointerDownEvent(DEFAULT_TAP_X * 2, DEFAULT_TAP_Y)); + SystemClock.sleep(ViewConfiguration.getLongPressTimeout() + 100); + } + 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 fbcde533aa9f..fcd16a09457e 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 @@ -39,10 +39,36 @@ public class TouchEventGenerator { return generateSingleTouchEvent(displayId, ACTION_DOWN, x, y); } + /** + * Create a test {@link MotionEvent#ACTION_POINTER_DOWN}, filling in all the basic values that + * define the motion. + * + * @param displayId The id of the display + * @param pointFs location on the screen of the all pointers + */ + public static MotionEvent pointerDownEvent(int displayId, PointF[] pointFs) { + final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int action = ACTION_POINTER_DOWN | actionIndex; + return generateMultiplePointersEvent(displayId, action, pointFs); + } + public static MotionEvent moveEvent(int displayId, float x, float y) { return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y); } + /** + * Create a test {@link MotionEvent#ACTION_POINTER_UP}, filling in all the basic values that + * define the motion. + * + * @param displayId the id of the display + * @param pointFs location on the screen of the all pointers + */ + public static MotionEvent pointerUpEvent(int displayId, PointF[] pointFs) { + final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int action = ACTION_POINTER_UP | actionIndex; + return generateMultiplePointersEvent(displayId, action, pointFs); + } + public static MotionEvent upEvent(int displayId, float x, float y) { return generateSingleTouchEvent(displayId, ACTION_UP, x, y); } |