| /** |
| * Copyright (C) 2017 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 android.accessibilityservice.cts; |
| |
| import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen; |
| import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession; |
| import static android.accessibilityservice.cts.utils.GestureUtils.add; |
| import static android.accessibilityservice.cts.utils.GestureUtils.click; |
| import static android.accessibilityservice.cts.utils.GestureUtils.diff; |
| import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf; |
| import static android.accessibilityservice.cts.utils.GestureUtils.getGestureBuilder; |
| import static android.accessibilityservice.cts.utils.GestureUtils.longClick; |
| import static android.accessibilityservice.cts.utils.GestureUtils.startingAt; |
| import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES; |
| |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.verify; |
| |
| import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; |
| import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; |
| import android.accessibilityservice.AccessibilityService; |
| import android.accessibilityservice.GestureDescription; |
| import android.accessibilityservice.GestureDescription.StrokeDescription; |
| import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity; |
| import android.accessibilityservice.cts.utils.GestureUtils; |
| import android.app.Activity; |
| import android.app.Instrumentation; |
| import android.app.UiAutomation; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.graphics.Path; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.platform.test.annotations.AppModeFull; |
| import android.util.DisplayMetrics; |
| import android.view.Display; |
| import android.view.ViewConfiguration; |
| import android.view.WindowManager; |
| import android.view.accessibility.AccessibilityEvent; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.RuleChain; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| /** Verify that motion events are recognized as accessibility gestures. */ |
| @RunWith(AndroidJUnit4.class) |
| public class AccessibilityGestureDetectorTest { |
| |
| // Constants |
| private static final float GESTURE_LENGTH_INCHES = 1.0f; |
| // The movement should exceed the threshold 1 cm in 150 ms defined in Swipe.java. It means the |
| // swipe velocity in testing should be greater than 2.54 cm / 381 ms. Therefore the |
| // duration should be smaller than 381. |
| private static final long STROKE_MS = 380; |
| private static final long GESTURE_DISPATCH_TIMEOUT_MS = 3000; |
| private static final long EVENT_DISPATCH_TIMEOUT_MS = 3000; |
| private static final PointF FINGER_OFFSET_PX = new PointF(100f, -50f); |
| |
| private static Instrumentation sInstrumentation; |
| private static UiAutomation sUiAutomation; |
| |
| private InstrumentedAccessibilityServiceTestRule<GestureDetectionStubAccessibilityService> |
| mServiceRule = new InstrumentedAccessibilityServiceTestRule<>( |
| GestureDetectionStubAccessibilityService.class, false); |
| |
| private AccessibilityDumpOnFailureRule mDumpOnFailureRule = |
| new AccessibilityDumpOnFailureRule(); |
| |
| @Rule |
| public final RuleChain mRuleChain = RuleChain |
| .outerRule(mServiceRule) |
| .around(mDumpOnFailureRule); |
| |
| // Test AccessibilityService that collects gestures. |
| GestureDetectionStubAccessibilityService mService; |
| boolean mHasTouchScreen; |
| boolean mScreenBigEnough; |
| int mStrokeLenPxX; // Gesture stroke size, in pixels |
| int mStrokeLenPxY; |
| Point mCenter; // Center of screen. Gestures all start from this point. |
| PointF mTapLocation; |
| int mScaledTouchSlop; |
| int mMaxAdjustedStrokeLenPxX; |
| int mMaxAdjustedStrokeLenPxY; |
| @Mock AccessibilityService.GestureResultCallback mGestureDispatchCallback; |
| |
| @BeforeClass |
| public static void oneTimeSetup() { |
| sInstrumentation = InstrumentationRegistry.getInstrumentation(); |
| sUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); |
| } |
| |
| @AfterClass |
| public static void finalTearDown() { |
| sUiAutomation.destroy(); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| |
| // Check that device has a touch screen. |
| PackageManager pm = sInstrumentation.getContext().getPackageManager(); |
| mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) |
| || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH); |
| if (!mHasTouchScreen) { |
| return; |
| } |
| |
| // Find screen size, check that it is big enough for gestures. |
| // Gestures will start in the center of the screen, so we need enough horiz/vert space. |
| WindowManager windowManager = (WindowManager) sInstrumentation.getContext() |
| .getSystemService(Context.WINDOW_SERVICE); |
| final DisplayMetrics metrics = new DisplayMetrics(); |
| windowManager.getDefaultDisplay().getRealMetrics(metrics); |
| mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2); |
| mTapLocation = new PointF(mCenter); |
| mScaledTouchSlop = |
| ViewConfiguration.get(sInstrumentation.getContext()).getScaledTouchSlop(); |
| mStrokeLenPxX = (int) (GESTURE_LENGTH_INCHES * metrics.xdpi); |
| // The threshold is determined by xdpi. |
| mStrokeLenPxY = mStrokeLenPxX; |
| mMaxAdjustedStrokeLenPxX = metrics.widthPixels / 2; |
| mMaxAdjustedStrokeLenPxY = metrics.heightPixels / 2; |
| final boolean screenWideEnough = metrics.widthPixels / 2 > mStrokeLenPxX; |
| final boolean screenHighEnough = metrics.heightPixels / 2 > mStrokeLenPxY; |
| mScreenBigEnough = screenWideEnough && screenHighEnough; |
| if (!mScreenBigEnough) { |
| return; |
| } |
| // Start stub accessibility service. |
| mService = mServiceRule.enableService(); |
| } |
| |
| @Test |
| @AppModeFull |
| public void testRecognizeGesturePath() { |
| if (!mHasTouchScreen || !mScreenBigEnough) { |
| return; |
| } |
| |
| runGestureDetectionTestOnDisplay(Display.DEFAULT_DISPLAY); |
| runMultiFingerGestureDetectionTestOnDisplay(Display.DEFAULT_DISPLAY); |
| } |
| |
| @Test |
| @AppModeFull |
| public void testRecognizeGesturePathOnVirtualDisplay() throws Exception { |
| assumeTrue(sInstrumentation.getContext().getPackageManager() |
| .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); |
| |
| if (!mHasTouchScreen || !mScreenBigEnough) { |
| return; |
| } |
| |
| try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) { |
| final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait( |
| sInstrumentation.getTargetContext(), false).getDisplayId(); |
| // Launches an activity on virtual display to meet a real situation. |
| final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen( |
| sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class, |
| displayId); |
| |
| try { |
| runGestureDetectionTestOnDisplay(displayId); |
| runMultiFingerGestureDetectionTestOnDisplay(displayId); |
| } finally { |
| sInstrumentation.runOnMainSync(() -> { |
| activity.finish(); |
| }); |
| sInstrumentation.waitForIdleSync(); |
| } |
| } |
| } |
| |
| private void runGestureDetectionTestOnDisplay(int displayId) { |
| // Compute gesture stroke lengths, in pixels. |
| final int dx = mStrokeLenPxX; |
| final int dy = mStrokeLenPxY; |
| |
| // Test recognizing various gestures. |
| testGesture( |
| doubleTap(displayId), |
| AccessibilityService.GESTURE_DOUBLE_TAP, |
| displayId); |
| testGesture( |
| doubleTapAndHold(displayId), |
| AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD, |
| displayId); |
| testPath(p(-dx, +0), AccessibilityService.GESTURE_SWIPE_LEFT, displayId); |
| testPath(p(+dx, +0), AccessibilityService.GESTURE_SWIPE_RIGHT, displayId); |
| testPath(p(+0, -dy), AccessibilityService.GESTURE_SWIPE_UP, displayId); |
| testPath(p(+0, +dy), AccessibilityService.GESTURE_SWIPE_DOWN, displayId); |
| |
| testPath(p(-dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT, |
| displayId); |
| testPath(p(-dx, +0), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP, |
| displayId); |
| testPath(p(-dx, +0), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN, |
| displayId); |
| |
| testPath(p(+dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT, |
| displayId); |
| testPath(p(+dx, +0), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP, |
| displayId); |
| testPath(p(+dx, +0), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN, |
| displayId); |
| |
| testPath(p(+0, -dy), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT, |
| displayId); |
| testPath(p(+0, -dy), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT, |
| displayId); |
| testPath(p(+0, -dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN, |
| displayId); |
| |
| testPath(p(+0, +dy), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT, |
| displayId); |
| testPath(p(+0, +dy), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT, |
| displayId); |
| testPath(p(+0, +dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP, |
| displayId); |
| } |
| |
| private void runMultiFingerGestureDetectionTestOnDisplay(int displayId) { |
| // Compute gesture stroke lengths, in pixels. |
| final int dx = mStrokeLenPxX; |
| final int dy = mStrokeLenPxY; |
| testGesture( |
| twoFingerSingleTap(displayId), |
| AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP, |
| displayId); |
| testGesture( |
| twoFingerTripleTapAndHold(displayId), |
| AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, |
| displayId); |
| testGesture( |
| twoFingerDoubleTap(displayId), |
| AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP, |
| displayId); |
| testGesture( |
| twoFingerDoubleTapAndHold(displayId), |
| AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, |
| displayId); |
| testGesture( |
| twoFingerTripleTap(displayId), |
| AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP, |
| displayId); |
| |
| testGesture( |
| threeFingerSingleTap(displayId), |
| AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP, |
| displayId); |
| testGesture( |
| threeFingerSingleTapAndHold(displayId), |
| AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD, |
| displayId); |
| testGesture( |
| threeFingerDoubleTap(displayId), |
| AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP, |
| displayId); |
| testGesture( |
| threeFingerDoubleTapAndHold(displayId), |
| AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, |
| displayId); |
| testGesture( |
| threeFingerTripleTap(displayId), |
| AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP, |
| displayId); |
| testGesture( |
| threeFingerTripleTapAndHold(displayId), |
| AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD, |
| displayId); |
| |
| testGesture( |
| fourFingerSingleTap(displayId), |
| AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP, |
| displayId); |
| testGesture( |
| fourFingerDoubleTap(displayId), |
| AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP, |
| displayId); |
| testGesture( |
| fourFingerDoubleTapAndHold(displayId), |
| AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, |
| displayId); |
| testGesture( |
| fourFingerTripleTap(displayId), |
| AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP, |
| displayId); |
| |
| testGesture( |
| MultiFingerSwipe(displayId, 3, 0, dy), |
| AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 3, -dx, 0), |
| AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 3, dx, 0), |
| AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 3, 0, -dy), |
| AccessibilityService.GESTURE_3_FINGER_SWIPE_UP, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 4, 0, dy), |
| AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 4, -dx, 0), |
| AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 4, dx, 0), |
| AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT, |
| displayId); |
| testGesture( |
| MultiFingerSwipe(displayId, 4, 0, -dy), |
| AccessibilityService.GESTURE_4_FINGER_SWIPE_UP, |
| displayId); |
| } |
| |
| /** Convenient short alias to make a Point. */ |
| private static Point p(int x, int y) { |
| return new Point(x, y); |
| } |
| |
| /** Test recognizing path from PATH_START to PATH_START+delta on default display. */ |
| private void testPath(Point delta, int gestureId) { |
| testPath(delta, null, gestureId, Display.DEFAULT_DISPLAY); |
| } |
| |
| /** Test recognizing path from PATH_START to PATH_START+delta on specified display. */ |
| private void testPath(Point delta, int gestureId, int displayId) { |
| testPath(delta, null, gestureId, displayId); |
| } |
| /** Test recognizing path from PATH_START to PATH_START+delta on default display. */ |
| private void testPath(Point delta1, Point delta2, int gestureId) { |
| testPath(delta1, delta2, gestureId, Display.DEFAULT_DISPLAY); |
| } |
| |
| /** |
| * Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. on specified |
| * display. |
| */ |
| private void testPath(Point delta1, Point delta2, int gestureId, int displayId) { |
| // Create gesture motions. |
| int numPathSegments = (delta2 == null) ? 1 : 2; |
| long pathDurationMs = numPathSegments * STROKE_MS; |
| GestureDescription gesture = new GestureDescription.Builder() |
| .addStroke(new StrokeDescription( |
| linePath(mCenter, delta1, delta2), 0, pathDurationMs, false)) |
| .setDisplayId(displayId) |
| .build(); |
| |
| testGesture(gesture, gestureId, displayId); |
| } |
| |
| /** Dispatch a gesture and make sure it is detected as the specified gesture id. */ |
| private void testGesture(GestureDescription gesture, int gestureId, int displayId) { |
| // Dispatch gesture motions to specified display with GestureDescription.. |
| // Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync() |
| // because accessibility services read gesture events upstream from the point where |
| // sendPointerSync() injects events. |
| mService.runOnServiceSync(() -> |
| mService.dispatchGesture(gesture, mGestureDispatchCallback, null)); |
| verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce()) |
| .onCompleted(any()); |
| |
| // Wait for gesture recognizer, and check recognized gesture. |
| mService.assertGestureReceived(gestureId, displayId); |
| } |
| /** Create a path from startPoint, moving by delta1, then delta2. (delta2 may be null.) */ |
| Path linePath(Point startPoint, Point delta1, Point delta2) { |
| Path path = new Path(); |
| path.moveTo(startPoint.x, startPoint.y); |
| path.lineTo(startPoint.x + delta1.x, startPoint.y + delta1.y); |
| if (delta2 != null) { |
| path.lineTo(startPoint.x + delta2.x, startPoint.y + delta2.y); |
| } |
| return path; |
| } |
| |
| @Test |
| @AppModeFull |
| public void testVerifyGestureTouchEvent() { |
| if (!mHasTouchScreen || !mScreenBigEnough) { |
| return; |
| } |
| |
| verifyGestureTouchEventOnDisplay(Display.DEFAULT_DISPLAY); |
| verifyMultiFingerGestureTouchEventOnDisplay(Display.DEFAULT_DISPLAY); |
| } |
| |
| @Test |
| @AppModeFull |
| public void testVerifyGestureTouchEventOnVirtualDisplay() throws Exception { |
| assumeTrue(sInstrumentation.getContext().getPackageManager() |
| .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); |
| if (!mHasTouchScreen || !mScreenBigEnough) { |
| return; |
| } |
| |
| try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) { |
| final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait( |
| sInstrumentation.getTargetContext(), |
| false).getDisplayId(); |
| |
| // Launches an activity on virtual display to meet a real situation. |
| final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen( |
| sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class, |
| displayId); |
| try { |
| verifyGestureTouchEventOnDisplay(displayId); |
| verifyMultiFingerGestureTouchEventOnDisplay(displayId); |
| } finally { |
| sInstrumentation.runOnMainSync(() -> { |
| activity.finish(); |
| }); |
| sInstrumentation.waitForIdleSync(); |
| } |
| } |
| } |
| |
| private void verifyGestureTouchEventOnDisplay(int displayId) { |
| assertEventAfterGesture(swipe(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| |
| assertEventAfterGesture(tap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START, |
| AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| |
| assertEventAfterGesture(doubleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| |
| assertEventAfterGesture(doubleTapAndHold(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| } |
| |
| private void verifyMultiFingerGestureTouchEventOnDisplay(int displayId) { |
| assertEventAfterGesture(twoFingerSingleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| assertEventAfterGesture(twoFingerDoubleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| assertEventAfterGesture(twoFingerTripleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| |
| assertEventAfterGesture(threeFingerSingleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| assertEventAfterGesture(threeFingerDoubleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| assertEventAfterGesture(threeFingerTripleTap(displayId), |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, |
| AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); |
| } |
| |
| @Test |
| @AppModeFull |
| public void testDispatchGesture_privateDisplay_gestureCancelled() throws Exception{ |
| assumeTrue(sInstrumentation.getContext().getPackageManager() |
| .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); |
| if (!mHasTouchScreen || !mScreenBigEnough) { |
| return; |
| } |
| |
| try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) { |
| final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait |
| (sInstrumentation.getTargetContext(), |
| true).getDisplayId(); |
| GestureDescription gesture = swipe(displayId); |
| mService.clearGestures(); |
| mService.runOnServiceSync(() -> |
| mService.dispatchGesture(gesture, mGestureDispatchCallback, null)); |
| verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce()) |
| .onCancelled(any()); |
| } |
| } |
| |
| /** Test touch for accessibility events */ |
| private void assertEventAfterGesture(GestureDescription gesture, int... events) { |
| mService.clearEvents(); |
| mService.runOnServiceSync( |
| () -> mService.dispatchGesture(gesture, mGestureDispatchCallback, null)); |
| verify(mGestureDispatchCallback, timeout(EVENT_DISPATCH_TIMEOUT_MS).atLeastOnce()) |
| .onCompleted(any()); |
| |
| mService.assertPropagated(events); |
| } |
| |
| private GestureDescription swipe(int displayId) { |
| StrokeDescription swipe = new StrokeDescription( |
| linePath(mCenter, p(0, mStrokeLenPxY), null), 0, STROKE_MS, false); |
| return getGestureBuilder(displayId, swipe).build(); |
| } |
| |
| private GestureDescription tap(int displayId) { |
| StrokeDescription tap = click(mTapLocation); |
| return getGestureBuilder(displayId, tap).build(); |
| } |
| |
| private GestureDescription doubleTap(int displayId) { |
| StrokeDescription tap1 = click(mTapLocation); |
| StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation)); |
| return getGestureBuilder(displayId, tap1, tap2).build(); |
| } |
| |
| private GestureDescription doubleTapAndHold(int displayId) { |
| StrokeDescription tap1 = click(mTapLocation); |
| StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, longClick(mTapLocation)); |
| return getGestureBuilder(displayId, tap1, tap2).build(); |
| } |
| |
| private GestureDescription twoFingerSingleTap(int displayId) { |
| return multiFingerMultiTap(2, 1, displayId); |
| } |
| |
| private GestureDescription twoFingerTripleTapAndHold(int displayId) { |
| return multiFingerMultiTapAndHold(2, 3, displayId); |
| } |
| |
| private GestureDescription twoFingerDoubleTap(int displayId) { |
| return multiFingerMultiTap(2, 2, displayId); |
| } |
| |
| private GestureDescription twoFingerDoubleTapAndHold(int displayId) { |
| return multiFingerMultiTapAndHold(2, 2, displayId); |
| } |
| |
| private GestureDescription twoFingerTripleTap(int displayId) { |
| return multiFingerMultiTap(2, 3, displayId); |
| } |
| |
| private GestureDescription threeFingerSingleTap(int displayId) { |
| return multiFingerMultiTap(3, 1, displayId); |
| } |
| |
| private GestureDescription threeFingerSingleTapAndHold(int displayId) { |
| return multiFingerMultiTapAndHold(3, 1, displayId); |
| } |
| |
| private GestureDescription threeFingerDoubleTap(int displayId) { |
| return multiFingerMultiTap(3, 2, displayId); |
| } |
| |
| private GestureDescription threeFingerDoubleTapAndHold(int displayId) { |
| return multiFingerMultiTapAndHold(3, 2, displayId); |
| } |
| |
| private GestureDescription threeFingerTripleTap(int displayId) { |
| return multiFingerMultiTap(3, 3, displayId); |
| } |
| |
| private GestureDescription threeFingerTripleTapAndHold(int displayId) { |
| return multiFingerMultiTapAndHold(3, 3, displayId); |
| } |
| |
| private GestureDescription fourFingerSingleTap(int displayId) { |
| return multiFingerMultiTap(4, 1, displayId); |
| } |
| |
| private GestureDescription fourFingerDoubleTap(int displayId) { |
| return multiFingerMultiTap(4, 2, displayId); |
| } |
| |
| private GestureDescription fourFingerDoubleTapAndHold(int displayId) { |
| return multiFingerMultiTapAndHold(4, 2, displayId); |
| } |
| |
| private GestureDescription fourFingerTripleTap(int displayId) { |
| return multiFingerMultiTap(4, 3, displayId); |
| } |
| |
| private GestureDescription multiFingerMultiTap(int fingerCount, int tapCount, int displayId) { |
| // We dispatch the first finger, base, placed at left down side by an offset |
| // from the center of the display and the rest ones at right up side by delta |
| // from the base. |
| final PointF base = diff(mTapLocation, FINGER_OFFSET_PX); |
| return GestureUtils.multiFingerMultiTap( |
| base, FINGER_OFFSET_PX, fingerCount, tapCount, /* slop= */ 0, displayId); |
| } |
| |
| private GestureDescription multiFingerMultiTapAndHold( |
| int fingerCount, int tapCount, int displayId) { |
| // We dispatch the first finger, base, placed at left down side by an offset |
| // from the center of the display and the rest ones at right up side by delta |
| // from the base. |
| final PointF base = diff(mTapLocation, FINGER_OFFSET_PX); |
| return GestureUtils.multiFingerMultiTapAndHold( |
| base, FINGER_OFFSET_PX, fingerCount, tapCount, /* slop= */ 0, displayId); |
| } |
| |
| private GestureDescription MultiFingerSwipe( |
| int displayId, int fingerCount, float dx, float dy) { |
| float fingerOffset = 10f; |
| GestureDescription.Builder builder = new GestureDescription.Builder(); |
| builder.setDisplayId(displayId); |
| |
| // MultiFingerSwipe.java scales delta thresholds for multifinger gestures by multiplying |
| // the touch slop with the amount of fingers used in the gesture. |
| // With higher touch slops than default (8dp), the swipe lengths and duration needs to be |
| // adjusted in order for the a11y-service to interpret it as a swipe gesture. |
| float slopAdjustedDx = adjustStrokeDeltaForSlop(fingerCount, dx); |
| float slopAdjustedDy = adjustStrokeDeltaForSlop(fingerCount, dy); |
| long slopAdjustedStrokeDuration = Math.min( |
| adjustStrokeDurationForSlop(STROKE_MS, dx, slopAdjustedDx), |
| adjustStrokeDurationForSlop(STROKE_MS, dy, slopAdjustedDy)); |
| |
| final PointF tapLocation = new PointF(mTapLocation); |
| final float locationOffsetX = (fingerCount - 1) * fingerOffset; |
| tapLocation.offset(dx > 0 ? -locationOffsetX : locationOffsetX , 0); |
| for (int currentFinger = 0; currentFinger < fingerCount; ++currentFinger) { |
| // Make sure adjustments don't take us outside of screen boundaries. |
| assertTrue(slopAdjustedDx + (fingerOffset * currentFinger) < (mMaxAdjustedStrokeLenPxX |
| + locationOffsetX)); |
| assertTrue(slopAdjustedDy < mMaxAdjustedStrokeLenPxY); |
| builder.addStroke( |
| GestureUtils.swipe( |
| add(tapLocation, fingerOffset * currentFinger, 0), |
| add(tapLocation, slopAdjustedDx + (fingerOffset * currentFinger), |
| slopAdjustedDy), |
| slopAdjustedStrokeDuration)); |
| } |
| return builder.build(); |
| } |
| |
| private float adjustStrokeDeltaForSlop(int fingerCount, float strokeDelta) { |
| if (strokeDelta > 0.0f) { |
| return Math.max(strokeDelta, fingerCount * mScaledTouchSlop + 10); |
| } else if (strokeDelta < 0.0f) { |
| return Math.min(strokeDelta, -(fingerCount * mScaledTouchSlop + 10)); |
| } |
| return strokeDelta; |
| } |
| |
| private long adjustStrokeDurationForSlop( |
| long strokeDuration, float unadjustedDelta, float adjustedDelta) { |
| if (unadjustedDelta == 0.0f || adjustedDelta == 0.0f) { |
| return strokeDuration; |
| } |
| float absUnadjustedDelta = Math.abs(unadjustedDelta); |
| float absAdjustedDelta = Math.abs(adjustedDelta); |
| // Adjusted delta in this case, has additional delta added due to touch slop. |
| return Math.round((float) strokeDuration * absUnadjustedDelta / absAdjustedDelta); |
| } |
| } |