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