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