• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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