1 /** 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 * express or implied. See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 15 package android.accessibilityservice.cts; 16 17 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen; 18 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession; 19 import static android.accessibilityservice.cts.utils.GestureUtils.add; 20 import static android.accessibilityservice.cts.utils.GestureUtils.click; 21 import static android.accessibilityservice.cts.utils.GestureUtils.diff; 22 import static android.accessibilityservice.cts.utils.GestureUtils.getGestureBuilder; 23 import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES; 24 25 import static org.junit.Assume.assumeTrue; 26 import static org.mockito.Mockito.any; 27 import static org.mockito.Mockito.timeout; 28 import static org.mockito.Mockito.verify; 29 30 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; 31 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; 32 import android.accessibilityservice.AccessibilityService; 33 import android.accessibilityservice.GestureDescription; 34 import android.accessibilityservice.GestureDescription.StrokeDescription; 35 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity; 36 import android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession; 37 import android.accessibilityservice.cts.utils.GestureUtils; 38 import android.app.Activity; 39 import android.app.Instrumentation; 40 import android.app.UiAutomation; 41 import android.content.Context; 42 import android.content.pm.PackageManager; 43 import android.graphics.Path; 44 import android.graphics.Point; 45 import android.graphics.PointF; 46 import android.graphics.RectF; 47 import android.platform.test.annotations.AppModeFull; 48 import android.platform.test.annotations.Presubmit; 49 import android.util.DisplayMetrics; 50 import android.view.Display; 51 import android.view.ViewConfiguration; 52 import android.view.WindowManager; 53 import android.view.accessibility.AccessibilityEvent; 54 55 import androidx.test.InstrumentationRegistry; 56 import androidx.test.filters.FlakyTest; 57 import androidx.test.runner.AndroidJUnit4; 58 59 import com.android.compatibility.common.util.CddTest; 60 61 import org.junit.AfterClass; 62 import org.junit.Before; 63 import org.junit.BeforeClass; 64 import org.junit.Rule; 65 import org.junit.Test; 66 import org.junit.rules.RuleChain; 67 import org.junit.runner.RunWith; 68 import org.mockito.Mock; 69 import org.mockito.MockitoAnnotations; 70 71 /** Verify that motion events are recognized as accessibility gestures. */ 72 @RunWith(AndroidJUnit4.class) 73 @CddTest(requirements = {"3.10/C-1-1,C-1-2"}) 74 @Presubmit 75 public class AccessibilityGestureDetectorTest { 76 77 // Constants 78 private static final float GESTURE_LENGTH_INCHES = 1.0f; 79 // The movement should exceed the threshold 1 cm in 150 ms defined in Swipe.java. It means the 80 // swipe velocity in testing should be greater than 2.54 cm / 381 ms. Therefore the 81 // duration should be smaller than 381. 82 private static final long STROKE_MS = 380; 83 private static final long GESTURE_DISPATCH_TIMEOUT_MS = 3000; 84 private static final long EVENT_DISPATCH_TIMEOUT_MS = 3000; 85 private static final PointF FINGER_OFFSET_PX = new PointF(100f, -50f); 86 87 private static Instrumentation sInstrumentation; 88 private static UiAutomation sUiAutomation; 89 90 private InstrumentedAccessibilityServiceTestRule<GestureDetectionStubAccessibilityService> 91 mServiceRule = new InstrumentedAccessibilityServiceTestRule<>( 92 GestureDetectionStubAccessibilityService.class, false); 93 94 private AccessibilityDumpOnFailureRule mDumpOnFailureRule = 95 new AccessibilityDumpOnFailureRule(); 96 97 private GestureUtils.DumpOnFailureRule mGestureUtilsDumpOnFailureRule = 98 new GestureUtils.DumpOnFailureRule(); 99 100 @Rule 101 public final RuleChain mRuleChain = RuleChain 102 .outerRule(mServiceRule) 103 .around(mDumpOnFailureRule) 104 .around(mGestureUtilsDumpOnFailureRule); 105 106 // Test AccessibilityService that collects gestures. 107 GestureDetectionStubAccessibilityService mService; 108 boolean mHasTouchScreen; 109 boolean mScreenBigEnough; 110 int mStrokeLenPxX; // Gesture stroke size, in pixels 111 int mStrokeLenPxY; 112 Point mCenter; // Center of screen. Gestures all start from this point. 113 PointF mTapLocation; 114 int mScaledTouchSlop; 115 RectF mDisplayBounds; 116 int mMaxAdjustedStrokeLenPxX; 117 int mMaxAdjustedStrokeLenPxY; 118 @Mock AccessibilityService.GestureResultCallback mGestureDispatchCallback; 119 120 @BeforeClass oneTimeSetup()121 public static void oneTimeSetup() { 122 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 123 sUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); 124 GestureUtils.randomize(); 125 } 126 127 @AfterClass finalTearDown()128 public static void finalTearDown() { 129 sUiAutomation.destroy(); 130 } 131 132 @Before setUp()133 public void setUp() throws Exception { 134 MockitoAnnotations.initMocks(this); 135 136 // Check that device has a touch screen. 137 PackageManager pm = sInstrumentation.getContext().getPackageManager(); 138 mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) 139 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH); 140 if (!mHasTouchScreen) { 141 return; 142 } 143 144 // Find screen size, check that it is big enough for gestures. 145 // Gestures will start in the center of the screen, so we need enough horiz/vert space. 146 WindowManager windowManager = (WindowManager) sInstrumentation.getContext() 147 .getSystemService(Context.WINDOW_SERVICE); 148 final DisplayMetrics metrics = new DisplayMetrics(); 149 windowManager.getDefaultDisplay().getRealMetrics(metrics); 150 mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2); 151 mTapLocation = new PointF(mCenter); 152 mScaledTouchSlop = 153 ViewConfiguration.get(sInstrumentation.getContext()).getScaledTouchSlop(); 154 mStrokeLenPxX = (int) (GESTURE_LENGTH_INCHES * metrics.xdpi); 155 // The threshold is determined by xdpi. 156 mStrokeLenPxY = mStrokeLenPxX; 157 mDisplayBounds = new RectF(0.0f, 0.0f, (float) metrics.widthPixels, (float) metrics.heightPixels); 158 final boolean screenWideEnough = metrics.widthPixels / 2 > mStrokeLenPxX; 159 final boolean screenHighEnough = metrics.heightPixels / 2 > mStrokeLenPxY; 160 mScreenBigEnough = screenWideEnough && screenHighEnough; 161 if (!mScreenBigEnough) { 162 return; 163 } 164 // Start stub accessibility service. 165 mService = mServiceRule.enableService(); 166 } 167 168 @Test 169 @FlakyTest 170 @AppModeFull testRecognizeGesturePath()171 public void testRecognizeGesturePath() { 172 if (!mHasTouchScreen || !mScreenBigEnough) { 173 return; 174 } 175 176 runGestureDetectionTestOnDisplay(Display.DEFAULT_DISPLAY); 177 runMultiFingerGestureDetectionTestOnDisplay(Display.DEFAULT_DISPLAY); 178 } 179 180 @Test 181 @FlakyTest 182 @AppModeFull testRecognizeGesturePathOnVirtualDisplay()183 public void testRecognizeGesturePathOnVirtualDisplay() throws Exception { 184 assumeTrue(sInstrumentation.getContext().getPackageManager() 185 .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); 186 187 if (!mHasTouchScreen || !mScreenBigEnough) { 188 return; 189 } 190 191 try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) { 192 final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait( 193 sInstrumentation.getTargetContext(), false).getDisplayId(); 194 // Launches an activity on virtual display to meet a real situation. 195 final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen( 196 sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class, 197 displayId); 198 199 try { 200 runGestureDetectionTestOnDisplay(displayId); 201 runMultiFingerGestureDetectionTestOnDisplay(displayId); 202 } finally { 203 sInstrumentation.runOnMainSync(() -> { 204 activity.finish(); 205 }); 206 sInstrumentation.waitForIdleSync(); 207 } 208 } 209 } 210 runGestureDetectionTestOnDisplay(int displayId)211 private void runGestureDetectionTestOnDisplay(int displayId) { 212 // Compute gesture stroke lengths, in pixels. 213 final int dx = mStrokeLenPxX; 214 final int dy = mStrokeLenPxY; 215 216 // Test recognizing various gestures. 217 testGesture( 218 doubleTap(displayId), 219 AccessibilityService.GESTURE_DOUBLE_TAP, 220 displayId); 221 testGesture( 222 doubleTapAndHold(displayId), 223 AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD, 224 displayId); 225 testPath(p(-dx, +0), AccessibilityService.GESTURE_SWIPE_LEFT, displayId); 226 testPath(p(+dx, +0), AccessibilityService.GESTURE_SWIPE_RIGHT, displayId); 227 testPath(p(+0, -dy), AccessibilityService.GESTURE_SWIPE_UP, displayId); 228 testPath(p(+0, +dy), AccessibilityService.GESTURE_SWIPE_DOWN, displayId); 229 230 testPath(p(-dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT, 231 displayId); 232 testPath(p(-dx, +0), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP, 233 displayId); 234 testPath(p(-dx, +0), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN, 235 displayId); 236 237 testPath(p(+dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT, 238 displayId); 239 testPath(p(+dx, +0), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP, 240 displayId); 241 testPath(p(+dx, +0), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN, 242 displayId); 243 244 testPath(p(+0, -dy), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT, 245 displayId); 246 testPath(p(+0, -dy), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT, 247 displayId); 248 testPath(p(+0, -dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN, 249 displayId); 250 251 testPath(p(+0, +dy), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT, 252 displayId); 253 testPath(p(+0, +dy), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT, 254 displayId); 255 testPath(p(+0, +dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP, 256 displayId); 257 } 258 runMultiFingerGestureDetectionTestOnDisplay(int displayId)259 private void runMultiFingerGestureDetectionTestOnDisplay(int displayId) { 260 // Compute gesture stroke lengths, in pixels. 261 final int dx = mStrokeLenPxX; 262 final int dy = mStrokeLenPxY; 263 testGesture( 264 twoFingerSingleTap(displayId), 265 AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP, 266 displayId); 267 testGesture( 268 twoFingerTripleTapAndHold(displayId), 269 AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, 270 displayId); 271 testGesture( 272 twoFingerDoubleTap(displayId), 273 AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP, 274 displayId); 275 testGesture( 276 twoFingerDoubleTapAndHold(displayId), 277 AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, 278 displayId); 279 testGesture( 280 twoFingerTripleTap(displayId), 281 AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP, 282 displayId); 283 284 testGesture( 285 threeFingerSingleTap(displayId), 286 AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP, 287 displayId); 288 testGesture( 289 threeFingerSingleTapAndHold(displayId), 290 AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD, 291 displayId); 292 testGesture( 293 threeFingerDoubleTap(displayId), 294 AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP, 295 displayId); 296 testGesture( 297 threeFingerDoubleTapAndHold(displayId), 298 AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, 299 displayId); 300 testGesture( 301 threeFingerTripleTap(displayId), 302 AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP, 303 displayId); 304 testGesture( 305 threeFingerTripleTapAndHold(displayId), 306 AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD, 307 displayId); 308 309 testGesture( 310 fourFingerSingleTap(displayId), 311 AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP, 312 displayId); 313 testGesture( 314 fourFingerDoubleTap(displayId), 315 AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP, 316 displayId); 317 testGesture( 318 fourFingerDoubleTapAndHold(displayId), 319 AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, 320 displayId); 321 testGesture( 322 fourFingerTripleTap(displayId), 323 AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP, 324 displayId); 325 326 testMultiSwipeGesture( 327 displayId, 3, 0, dy, 328 AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN); 329 testMultiSwipeGesture( 330 displayId, 3, -dx, 0, 331 AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT); 332 testMultiSwipeGesture( 333 displayId, 3, dx, 0, 334 AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT); 335 testMultiSwipeGesture( 336 displayId, 3, 0, -dy, 337 AccessibilityService.GESTURE_3_FINGER_SWIPE_UP); 338 testMultiSwipeGesture( 339 displayId, 4, 0, dy, 340 AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN); 341 testMultiSwipeGesture( 342 displayId, 4, -dx, 0, 343 AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT); 344 testMultiSwipeGesture( 345 displayId, 4, dx, 0, 346 AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT); 347 testMultiSwipeGesture( 348 displayId, 4, 0, -dy, 349 AccessibilityService.GESTURE_4_FINGER_SWIPE_UP); 350 } 351 352 /** Convenient short alias to make a Point. */ p(int x, int y)353 private static Point p(int x, int y) { 354 return new Point(x, y); 355 } 356 357 /** Test recognizing path from PATH_START to PATH_START+delta on default display. */ testPath(Point delta, int gestureId)358 private void testPath(Point delta, int gestureId) { 359 testPath(delta, null, gestureId, Display.DEFAULT_DISPLAY); 360 } 361 362 /** Test recognizing path from PATH_START to PATH_START+delta on specified display. */ testPath(Point delta, int gestureId, int displayId)363 private void testPath(Point delta, int gestureId, int displayId) { 364 testPath(delta, null, gestureId, displayId); 365 } 366 /** Test recognizing path from PATH_START to PATH_START+delta on default display. */ testPath(Point delta1, Point delta2, int gestureId)367 private void testPath(Point delta1, Point delta2, int gestureId) { 368 testPath(delta1, delta2, gestureId, Display.DEFAULT_DISPLAY); 369 } 370 371 /** 372 * Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. on specified 373 * display. 374 */ testPath(Point delta1, Point delta2, int gestureId, int displayId)375 private void testPath(Point delta1, Point delta2, int gestureId, int displayId) { 376 // Create gesture motions. 377 int numPathSegments = (delta2 == null) ? 1 : 2; 378 long pathDurationMs = numPathSegments * STROKE_MS; 379 GestureDescription gesture = new GestureDescription.Builder() 380 .addStroke(new StrokeDescription( 381 linePath(mCenter, delta1, delta2), 0, pathDurationMs, false)) 382 .setDisplayId(displayId) 383 .build(); 384 385 testGesture(gesture, gestureId, displayId); 386 } 387 388 /** Dispatch a gesture and make sure it is detected as the specified gesture id. */ testGesture(GestureDescription gesture, int gestureId, int displayId)389 private void testGesture(GestureDescription gesture, int gestureId, int displayId) { 390 // Dispatch gesture motions to specified display with GestureDescription.. 391 // Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync() 392 // because accessibility services read gesture events upstream from the point where 393 // sendPointerSync() injects events. 394 mService.runOnServiceSync(() -> 395 mService.dispatchGesture(gesture, mGestureDispatchCallback, null)); 396 verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce()) 397 .onCompleted(any()); 398 399 // Wait for gesture recognizer, and check recognized gesture. 400 mService.assertGestureReceived(gestureId, displayId); 401 } 402 testMultiSwipeGesture( int displayId, int fingerCount, int dx, int dy, int gestureId)403 private void testMultiSwipeGesture( 404 int displayId, int fingerCount, int dx, int dy, int gestureId) { 405 GestureDescription gesture = MultiFingerSwipe(displayId, fingerCount, dx, dy); 406 if (gesture != null) { 407 testGesture(gesture, gestureId, displayId); 408 } 409 } 410 411 /** Create a path from startPoint, moving by delta1, then delta2. (delta2 may be null.) */ linePath(Point startPoint, Point delta1, Point delta2)412 Path linePath(Point startPoint, Point delta1, Point delta2) { 413 Path path = new Path(); 414 path.moveTo(startPoint.x, startPoint.y); 415 path.lineTo(startPoint.x + delta1.x, startPoint.y + delta1.y); 416 if (delta2 != null) { 417 path.lineTo(startPoint.x + delta2.x, startPoint.y + delta2.y); 418 } 419 return path; 420 } 421 422 @Test 423 @AppModeFull testVerifyGestureTouchEvent()424 public void testVerifyGestureTouchEvent() { 425 if (!mHasTouchScreen || !mScreenBigEnough) { 426 return; 427 } 428 429 verifyGestureTouchEventOnDisplay(Display.DEFAULT_DISPLAY); 430 verifyMultiFingerGestureTouchEventOnDisplay(Display.DEFAULT_DISPLAY); 431 } 432 433 @Test 434 @AppModeFull testVerifyGestureTouchEventOnVirtualDisplay()435 public void testVerifyGestureTouchEventOnVirtualDisplay() throws Exception { 436 assumeTrue(sInstrumentation.getContext().getPackageManager() 437 .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); 438 if (!mHasTouchScreen || !mScreenBigEnough) { 439 return; 440 } 441 442 try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) { 443 final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait( 444 sInstrumentation.getTargetContext(), 445 false).getDisplayId(); 446 447 // Launches an activity on virtual display to meet a real situation. 448 final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen( 449 sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class, 450 displayId); 451 try { 452 verifyGestureTouchEventOnDisplay(displayId); 453 verifyMultiFingerGestureTouchEventOnDisplay(displayId); 454 } finally { 455 sInstrumentation.runOnMainSync(() -> { 456 activity.finish(); 457 }); 458 sInstrumentation.waitForIdleSync(); 459 } 460 } 461 } 462 verifyGestureTouchEventOnDisplay(int displayId)463 private void verifyGestureTouchEventOnDisplay(int displayId) { 464 assertEventAfterGesture(swipe(displayId), 465 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 466 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 467 468 assertEventAfterGesture(tap(displayId), 469 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 470 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START, 471 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END, 472 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 473 474 assertEventAfterGesture(doubleTap(displayId), 475 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 476 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 477 478 assertEventAfterGesture(doubleTapAndHold(displayId), 479 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 480 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 481 } 482 verifyMultiFingerGestureTouchEventOnDisplay(int displayId)483 private void verifyMultiFingerGestureTouchEventOnDisplay(int displayId) { 484 assertEventAfterGesture(twoFingerSingleTap(displayId), 485 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 486 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 487 assertEventAfterGesture(twoFingerDoubleTap(displayId), 488 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 489 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 490 assertEventAfterGesture(twoFingerTripleTap(displayId), 491 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 492 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 493 494 assertEventAfterGesture(threeFingerSingleTap(displayId), 495 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 496 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 497 assertEventAfterGesture(threeFingerDoubleTap(displayId), 498 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 499 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 500 assertEventAfterGesture(threeFingerTripleTap(displayId), 501 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START, 502 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 503 } 504 505 @Test 506 @AppModeFull testDispatchGesture_privateDisplay_gestureCancelled()507 public void testDispatchGesture_privateDisplay_gestureCancelled() throws Exception{ 508 assumeTrue(sInstrumentation.getContext().getPackageManager() 509 .hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); 510 if (!mHasTouchScreen || !mScreenBigEnough) { 511 return; 512 } 513 514 try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) { 515 final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait 516 (sInstrumentation.getTargetContext(), 517 true).getDisplayId(); 518 GestureDescription gesture = swipe(displayId); 519 mService.clearGestures(); 520 mService.runOnServiceSync(() -> 521 mService.dispatchGesture(gesture, mGestureDispatchCallback, null)); 522 verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce()) 523 .onCancelled(any()); 524 } 525 } 526 527 /** Test touch for accessibility events */ assertEventAfterGesture(GestureDescription gesture, int... events)528 private void assertEventAfterGesture(GestureDescription gesture, int... events) { 529 mService.clearEvents(); 530 mService.runOnServiceSync( 531 () -> mService.dispatchGesture(gesture, mGestureDispatchCallback, null)); 532 verify(mGestureDispatchCallback, timeout(EVENT_DISPATCH_TIMEOUT_MS).atLeastOnce()) 533 .onCompleted(any()); 534 535 mService.assertPropagated(events); 536 } 537 swipe(int displayId)538 private GestureDescription swipe(int displayId) { 539 StrokeDescription swipe = new StrokeDescription( 540 linePath(mCenter, p(0, mStrokeLenPxY), null), 0, STROKE_MS, false); 541 return getGestureBuilder(displayId, swipe).build(); 542 } 543 tap(int displayId)544 private GestureDescription tap(int displayId) { 545 StrokeDescription tap = click(mTapLocation); 546 return getGestureBuilder(displayId, tap).build(); 547 } 548 doubleTap(int displayId)549 private GestureDescription doubleTap(int displayId) { 550 return GestureUtils.doubleTap(mTapLocation, displayId); 551 } 552 doubleTapAndHold(int displayId)553 private GestureDescription doubleTapAndHold(int displayId) { 554 return GestureUtils.doubleTapAndHold(mTapLocation, displayId); 555 } 556 twoFingerSingleTap(int displayId)557 private GestureDescription twoFingerSingleTap(int displayId) { 558 return multiFingerMultiTap(2, 1, displayId); 559 } 560 twoFingerTripleTapAndHold(int displayId)561 private GestureDescription twoFingerTripleTapAndHold(int displayId) { 562 return multiFingerMultiTapAndHold(2, 3, displayId); 563 } 564 twoFingerDoubleTap(int displayId)565 private GestureDescription twoFingerDoubleTap(int displayId) { 566 return multiFingerMultiTap(2, 2, displayId); 567 } 568 twoFingerDoubleTapAndHold(int displayId)569 private GestureDescription twoFingerDoubleTapAndHold(int displayId) { 570 return multiFingerMultiTapAndHold(2, 2, displayId); 571 } 572 twoFingerTripleTap(int displayId)573 private GestureDescription twoFingerTripleTap(int displayId) { 574 return multiFingerMultiTap(2, 3, displayId); 575 } 576 threeFingerSingleTap(int displayId)577 private GestureDescription threeFingerSingleTap(int displayId) { 578 return multiFingerMultiTap(3, 1, displayId); 579 } 580 threeFingerSingleTapAndHold(int displayId)581 private GestureDescription threeFingerSingleTapAndHold(int displayId) { 582 return multiFingerMultiTapAndHold(3, 1, displayId); 583 } 584 threeFingerDoubleTap(int displayId)585 private GestureDescription threeFingerDoubleTap(int displayId) { 586 return multiFingerMultiTap(3, 2, displayId); 587 } 588 threeFingerDoubleTapAndHold(int displayId)589 private GestureDescription threeFingerDoubleTapAndHold(int displayId) { 590 return multiFingerMultiTapAndHold(3, 2, displayId); 591 } 592 threeFingerTripleTap(int displayId)593 private GestureDescription threeFingerTripleTap(int displayId) { 594 return multiFingerMultiTap(3, 3, displayId); 595 } 596 threeFingerTripleTapAndHold(int displayId)597 private GestureDescription threeFingerTripleTapAndHold(int displayId) { 598 return multiFingerMultiTapAndHold(3, 3, displayId); 599 } 600 fourFingerSingleTap(int displayId)601 private GestureDescription fourFingerSingleTap(int displayId) { 602 return multiFingerMultiTap(4, 1, displayId); 603 } 604 fourFingerDoubleTap(int displayId)605 private GestureDescription fourFingerDoubleTap(int displayId) { 606 return multiFingerMultiTap(4, 2, displayId); 607 } 608 fourFingerDoubleTapAndHold(int displayId)609 private GestureDescription fourFingerDoubleTapAndHold(int displayId) { 610 return multiFingerMultiTapAndHold(4, 2, displayId); 611 } 612 fourFingerTripleTap(int displayId)613 private GestureDescription fourFingerTripleTap(int displayId) { 614 return multiFingerMultiTap(4, 3, displayId); 615 } 616 multiFingerMultiTap(int fingerCount, int tapCount, int displayId)617 private GestureDescription multiFingerMultiTap(int fingerCount, int tapCount, int displayId) { 618 // We dispatch the first finger, base, placed at left down side by an offset 619 // from the center of the display and the rest ones at right up side by delta 620 // from the base. 621 final PointF base = diff(mTapLocation, FINGER_OFFSET_PX); 622 return GestureUtils.multiFingerMultiTap( 623 base, FINGER_OFFSET_PX, fingerCount, tapCount, /* slop= */ 0, displayId); 624 } 625 multiFingerMultiTapAndHold( int fingerCount, int tapCount, int displayId)626 private GestureDescription multiFingerMultiTapAndHold( 627 int fingerCount, int tapCount, int displayId) { 628 // We dispatch the first finger, base, placed at left down side by an offset 629 // from the center of the display and the rest ones at right up side by delta 630 // from the base. 631 final PointF base = diff(mTapLocation, FINGER_OFFSET_PX); 632 return GestureUtils.multiFingerMultiTapAndHold( 633 base, FINGER_OFFSET_PX, fingerCount, tapCount, /* slop= */ 0, displayId); 634 } 635 MultiFingerSwipe( int displayId, int fingerCount, float dx, float dy)636 private GestureDescription MultiFingerSwipe( 637 int displayId, int fingerCount, float dx, float dy) { 638 float fingerOffset = 10f; 639 GestureDescription.Builder builder = new GestureDescription.Builder(); 640 builder.setDisplayId(displayId); 641 642 // MultiFingerSwipe.java scales delta thresholds for multifinger gestures by multiplying 643 // the touch slop with the amount of fingers used in the gesture. 644 // With higher touch slops than default (8dp), the swipe lengths and duration needs to be 645 // adjusted in order for the a11y-service to interpret it as a swipe gesture. 646 float slopAdjustedDx = adjustStrokeDeltaForSlop(fingerCount, dx); 647 float slopAdjustedDy = adjustStrokeDeltaForSlop(fingerCount, dy); 648 long slopAdjustedStrokeDuration = Math.min( 649 adjustStrokeDurationForSlop(STROKE_MS, dx, slopAdjustedDx), 650 adjustStrokeDurationForSlop(STROKE_MS, dy, slopAdjustedDy)); 651 652 final float fingerOffsetSum = (fingerCount - 1) * fingerOffset; 653 final PointF tapLocation = new PointF(mTapLocation); 654 655 // Center the length of the swipe gesture on screen, instead of starting at the centre. 656 // This includes extra room required for multiple fingers. 657 adjustTapLocation(tapLocation, fingerOffsetSum, slopAdjustedDx, slopAdjustedDy); 658 659 // If the tap location is out of bounds, there is no room for this manoeuvre. 660 if (!mDisplayBounds.contains(tapLocation.x, tapLocation.y)) { 661 return null; 662 } 663 for (int currentFinger = 0; currentFinger < fingerCount; ++currentFinger) { 664 builder.addStroke( 665 GestureUtils.swipe( 666 add(tapLocation, fingerOffset * currentFinger, 0), 667 add(tapLocation, slopAdjustedDx + (fingerOffset * currentFinger), 668 slopAdjustedDy), 669 slopAdjustedStrokeDuration)); 670 } 671 return builder.build(); 672 } 673 adjustStrokeDeltaForSlop(int fingerCount, float strokeDelta)674 private float adjustStrokeDeltaForSlop(int fingerCount, float strokeDelta) { 675 if (strokeDelta > 0.0f) { 676 return strokeDelta + (fingerCount * mScaledTouchSlop); 677 } else if (strokeDelta < 0.0f) { 678 return strokeDelta - (fingerCount * mScaledTouchSlop); 679 } 680 return strokeDelta; 681 } 682 adjustStrokeDurationForSlop( long strokeDuration, float unadjustedDelta, float adjustedDelta)683 private long adjustStrokeDurationForSlop( 684 long strokeDuration, float unadjustedDelta, float adjustedDelta) { 685 if (unadjustedDelta == 0.0f || adjustedDelta == 0.0f) { 686 return strokeDuration; 687 } 688 float absUnadjustedDelta = Math.abs(unadjustedDelta); 689 float absAdjustedDelta = Math.abs(adjustedDelta); 690 // Adjusted delta in this case, has additional delta added due to touch slop. 691 return Math.round((float) strokeDuration * absUnadjustedDelta / absAdjustedDelta); 692 } 693 adjustTapLocation( PointF tapLocation, float fingerOffsetSum, float strokeDeltaX, float strokeDeltaY)694 private void adjustTapLocation( 695 PointF tapLocation, float fingerOffsetSum, float strokeDeltaX, float strokeDeltaY) { 696 float offsetX = 0.0f; 697 float offsetY = 0.0f; 698 if (strokeDeltaX > 0.0f) { 699 offsetX = (strokeDeltaX + fingerOffsetSum) / -2.0f; 700 } else if (strokeDeltaX < 0.0f) { 701 offsetX = (strokeDeltaX - fingerOffsetSum) / -2.0f; 702 } 703 if (strokeDeltaY > 0.0f) { 704 offsetY = (strokeDeltaY + fingerOffsetSum) / -2.0f; 705 } else if (strokeDeltaY < 0.0f) { 706 offsetY = (strokeDeltaY - fingerOffsetSum) / -2.0f; 707 } 708 tapLocation.offset(offsetX, offsetY); 709 } 710 } 711