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