1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.accessibility;
18 
19 import static android.view.MotionEvent.ACTION_DOWN;
20 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
21 import static android.view.MotionEvent.ACTION_UP;
22 import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
23 
24 import static org.hamcrest.CoreMatchers.allOf;
25 import static org.hamcrest.CoreMatchers.anyOf;
26 import static org.hamcrest.CoreMatchers.everyItem;
27 import static org.hamcrest.MatcherAssert.assertThat;
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertTrue;
31 import static org.mockito.Matchers.anyBoolean;
32 import static org.mockito.Matchers.anyInt;
33 import static org.mockito.Matchers.eq;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.reset;
36 import static org.mockito.Mockito.times;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.verifyNoMoreInteractions;
39 import static org.mockito.Mockito.verifyZeroInteractions;
40 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
41 
42 import android.accessibilityservice.GestureDescription.GestureStep;
43 import android.accessibilityservice.GestureDescription.TouchPoint;
44 import android.accessibilityservice.IAccessibilityServiceClient;
45 import android.graphics.Point;
46 import android.os.Handler;
47 import android.os.Message;
48 import android.os.RemoteException;
49 import android.util.Log;
50 import android.view.InputDevice;
51 import android.view.KeyEvent;
52 import android.view.MotionEvent;
53 import android.view.accessibility.AccessibilityEvent;
54 
55 import androidx.test.runner.AndroidJUnit4;
56 
57 import org.hamcrest.Description;
58 import org.hamcrest.Matcher;
59 import org.hamcrest.TypeSafeMatcher;
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.mockito.ArgumentCaptor;
65 
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.List;
69 
70 /**
71  * Tests for MotionEventInjector
72  */
73 @RunWith(AndroidJUnit4.class)
74 public class MotionEventInjectorTest {
75     private static final String LOG_TAG = "MotionEventInjectorTest";
76     private static final Matcher<MotionEvent> IS_ACTION_DOWN =
77             new MotionEventActionMatcher(ACTION_DOWN);
78     private static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
79             new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_DOWN);
80     private static final Matcher<MotionEvent> IS_ACTION_UP =
81             new MotionEventActionMatcher(ACTION_UP);
82     private static final Matcher<MotionEvent> IS_ACTION_POINTER_UP =
83             new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_UP);
84     private static final Matcher<MotionEvent> IS_ACTION_CANCEL =
85             new MotionEventActionMatcher(MotionEvent.ACTION_CANCEL);
86     private static final Matcher<MotionEvent> IS_ACTION_MOVE =
87             new MotionEventActionMatcher(MotionEvent.ACTION_MOVE);
88 
89     private static final Point LINE_START = new Point(100, 200);
90     private static final Point LINE_END = new Point(100, 300);
91     private static final int LINE_DURATION = 100;
92     private static final int LINE_SEQUENCE = 50;
93 
94     private static final Point CLICK_POINT = new Point(1000, 2000);
95     private static final int CLICK_DURATION = 10;
96     private static final int CLICK_SEQUENCE = 51;
97 
98     private static final int MOTION_EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
99     private static final int OTHER_EVENT_SOURCE = InputDevice.SOURCE_MOUSE;
100 
101     private static final Point CONTINUED_LINE_START = new Point(500, 300);
102     private static final Point CONTINUED_LINE_MID1 = new Point(500, 400);
103     private static final Point CONTINUED_LINE_MID2 = new Point(600, 300);
104     private static final Point CONTINUED_LINE_END = new Point(600, 400);
105     private static final int CONTINUED_LINE_STROKE_ID_1 = 100;
106     private static final int CONTINUED_LINE_STROKE_ID_2 = 101;
107     private static final int CONTINUED_LINE_INTERVAL = 100;
108     private static final int CONTINUED_LINE_SEQUENCE_1 = 52;
109     private static final int CONTINUED_LINE_SEQUENCE_2 = 53;
110 
111     MotionEventInjector mMotionEventInjector;
112     IAccessibilityServiceClient mServiceInterface;
113     List<GestureStep> mLineList = new ArrayList<>();
114     List<GestureStep> mClickList = new ArrayList<>();
115     List<GestureStep> mContinuedLineList1 = new ArrayList<>();
116     List<GestureStep> mContinuedLineList2 = new ArrayList<>();
117 
118     MotionEvent mClickDownEvent;
119     MotionEvent mClickUpEvent;
120     MotionEvent mHoverMoveEvent;
121 
122     ArgumentCaptor<MotionEvent> mCaptor1 = ArgumentCaptor.forClass(MotionEvent.class);
123     ArgumentCaptor<MotionEvent> mCaptor2 = ArgumentCaptor.forClass(MotionEvent.class);
124     MessageCapturingHandler mMessageCapturingHandler;
125     Matcher<MotionEvent> mIsLineStart;
126     Matcher<MotionEvent> mIsLineMiddle;
127     Matcher<MotionEvent> mIsLineEnd;
128     Matcher<MotionEvent> mIsClickDown;
129     Matcher<MotionEvent> mIsClickUp;
130 
131     @Before
setUp()132     public void setUp() {
133         mMessageCapturingHandler = new MessageCapturingHandler(new Handler.Callback() {
134             @Override
135             public boolean handleMessage(Message msg) {
136                 return mMotionEventInjector.handleMessage(msg);
137             }
138         });
139         mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
140         mServiceInterface = mock(IAccessibilityServiceClient.class);
141 
142         mLineList = createSimpleGestureFromPoints(0, 0, false, LINE_DURATION, LINE_START, LINE_END);
143         mClickList = createSimpleGestureFromPoints(
144                 0, 0, false, CLICK_DURATION, CLICK_POINT, CLICK_POINT);
145         mContinuedLineList1 = createSimpleGestureFromPoints(CONTINUED_LINE_STROKE_ID_1, 0, true,
146                 CONTINUED_LINE_INTERVAL, CONTINUED_LINE_START, CONTINUED_LINE_MID1);
147         mContinuedLineList2 = createSimpleGestureFromPoints(CONTINUED_LINE_STROKE_ID_2,
148                 CONTINUED_LINE_STROKE_ID_1, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID1,
149                 CONTINUED_LINE_MID2, CONTINUED_LINE_END);
150 
151         mClickDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, CLICK_POINT.x, CLICK_POINT.y, 0);
152         mClickDownEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
153         mClickUpEvent = MotionEvent.obtain(0, CLICK_DURATION, ACTION_UP, CLICK_POINT.x,
154                 CLICK_POINT.y, 0);
155         mClickUpEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
156 
157         mHoverMoveEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, CLICK_POINT.x, CLICK_POINT.y,
158                 0);
159         mHoverMoveEvent.setSource(InputDevice.SOURCE_MOUSE);
160 
161         mIsLineStart = allOf(IS_ACTION_DOWN, isAtPoint(LINE_START), hasStandardInitialization(),
162                 hasTimeFromDown(0));
163         mIsLineMiddle = allOf(IS_ACTION_MOVE, isAtPoint(LINE_END), hasStandardInitialization(),
164                 hasTimeFromDown(LINE_DURATION));
165         mIsLineEnd = allOf(IS_ACTION_UP, isAtPoint(LINE_END), hasStandardInitialization(),
166                 hasTimeFromDown(LINE_DURATION));
167         mIsClickDown = allOf(IS_ACTION_DOWN, isAtPoint(CLICK_POINT), hasStandardInitialization(),
168                 hasTimeFromDown(0));
169         mIsClickUp = allOf(IS_ACTION_UP, isAtPoint(CLICK_POINT), hasStandardInitialization(),
170                 hasTimeFromDown(CLICK_DURATION));
171     }
172 
173     @After
tearDown()174     public void tearDown() {
175         mMessageCapturingHandler.removeAllMessages();
176     }
177 
178 
179     @Test
testInjectEvents_shouldEmergeInOrderWithCorrectTiming()180     public void testInjectEvents_shouldEmergeInOrderWithCorrectTiming() throws RemoteException {
181         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
182         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
183         verifyNoMoreInteractions(next);
184         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
185 
186         verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER));
187         verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart),
188                 eq(FLAG_PASS_TO_USER));
189         verifyNoMoreInteractions(next);
190         reset(next);
191 
192         Matcher<MotionEvent> hasRightDownTime = hasDownTime(mCaptor1.getValue().getDownTime());
193 
194         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
195         verify(next).onMotionEvent(argThat(allOf(mIsLineMiddle, hasRightDownTime)),
196                 argThat(allOf(mIsLineMiddle, hasRightDownTime)), eq(FLAG_PASS_TO_USER));
197         verifyNoMoreInteractions(next);
198         reset(next);
199 
200         verifyZeroInteractions(mServiceInterface);
201 
202         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
203         verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)),
204                 argThat(allOf(mIsLineEnd, hasRightDownTime)), eq(FLAG_PASS_TO_USER));
205         verifyNoMoreInteractions(next);
206 
207         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
208         verifyNoMoreInteractions(mServiceInterface);
209     }
210 
211     @Test
testInjectEvents_gestureWithTooManyPoints_shouldNotCrash()212     public void testInjectEvents_gestureWithTooManyPoints_shouldNotCrash() throws  Exception {
213         int tooManyPointsCount = 20;
214         TouchPoint[] startTouchPoints = new TouchPoint[tooManyPointsCount];
215         TouchPoint[] endTouchPoints = new TouchPoint[tooManyPointsCount];
216         for (int i = 0; i < tooManyPointsCount; i++) {
217             startTouchPoints[i] = new TouchPoint();
218             startTouchPoints[i].mIsStartOfPath = true;
219             startTouchPoints[i].mX = i;
220             startTouchPoints[i].mY = i;
221             endTouchPoints[i] = new TouchPoint();
222             endTouchPoints[i].mIsEndOfPath = true;
223             endTouchPoints[i].mX = i;
224             endTouchPoints[i].mY = i;
225         }
226         List<GestureStep> events = Arrays.asList(
227                 new GestureStep(0, tooManyPointsCount, startTouchPoints),
228                 new GestureStep(CLICK_DURATION, tooManyPointsCount, endTouchPoints));
229         attachMockNext(mMotionEventInjector);
230         injectEventsSync(events, mServiceInterface, CLICK_SEQUENCE);
231         mMessageCapturingHandler.sendAllMessages();
232         verify(mServiceInterface).onPerformGestureResult(eq(CLICK_SEQUENCE), anyBoolean());
233     }
234 
235     @Test
testRegularEvent_afterGestureComplete_shouldPassToNext()236     public void testRegularEvent_afterGestureComplete_shouldPassToNext() {
237         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
238         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
239         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
240         reset(next);
241         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
242         verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown), eq(0));
243     }
244 
245     @Test
testInjectEvents_withRealGestureUnderway_shouldCancelRealAndPassInjected()246     public void testInjectEvents_withRealGestureUnderway_shouldCancelRealAndPassInjected() {
247         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
248         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
249         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
250 
251         verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
252         assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
253         assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
254         reset(next);
255 
256         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
257         verify(next).onMotionEvent(
258                 argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER));
259     }
260 
261     @Test
testInjectEvents_withRealMouseGestureUnderway_shouldContinueRealAndPassInjected()262     public void testInjectEvents_withRealMouseGestureUnderway_shouldContinueRealAndPassInjected() {
263         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
264         MotionEvent mouseEvent = MotionEvent.obtain(mClickDownEvent);
265         mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
266         MotionEventMatcher isMouseEvent = new MotionEventMatcher(mouseEvent);
267         mMotionEventInjector.onMotionEvent(mouseEvent, mouseEvent, 0);
268         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
269 
270         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
271         verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
272         assertThat(mCaptor1.getAllValues().get(0), isMouseEvent);
273         assertThat(mCaptor1.getAllValues().get(1), mIsLineStart);
274     }
275 
276     @Test
testInjectEvents_withRealGestureFinished_shouldJustPassInjected()277     public void testInjectEvents_withRealGestureFinished_shouldJustPassInjected() {
278         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
279         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
280         mMotionEventInjector.onMotionEvent(mClickUpEvent, mClickUpEvent, 0);
281 
282         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
283         verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
284         assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
285         assertThat(mCaptor1.getAllValues().get(1), mIsClickUp);
286         reset(next);
287 
288         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
289         verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER));
290         verify(next).onMotionEvent(
291                 argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER));
292     }
293 
294     @Test
testOnMotionEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassReal()295     public void testOnMotionEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassReal()
296             throws RemoteException {
297         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
298         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
299         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
300         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
301 
302         verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
303         assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
304         assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
305         assertThat(mCaptor1.getAllValues().get(2), mIsClickDown);
306         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
307     }
308 
309     @Test
310     public void
testOnMotionEvents_fromMouseWithInjectedGestureInProgress_shouldNotCancelAndPassReal()311             testOnMotionEvents_fromMouseWithInjectedGestureInProgress_shouldNotCancelAndPassReal()
312             throws RemoteException {
313         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
314         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
315         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
316         mMotionEventInjector.onMotionEvent(mHoverMoveEvent, mHoverMoveEvent, 0);
317         mMessageCapturingHandler.sendAllMessages();
318 
319         verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
320         assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
321         assertThat(mCaptor1.getAllValues().get(1), mIsLineMiddle);
322         assertThat(mCaptor1.getAllValues().get(2), mIsLineEnd);
323         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
324     }
325 
326     @Test
testOnMotionEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassReal()327     public void testOnMotionEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassReal()
328             throws RemoteException {
329         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
330         // Tack a click down to the end of the line
331         TouchPoint clickTouchPoint = new TouchPoint();
332         clickTouchPoint.mIsStartOfPath = true;
333         clickTouchPoint.mX = CLICK_POINT.x;
334         clickTouchPoint.mY = CLICK_POINT.y;
335         mLineList.add(new GestureStep(0, 1, new TouchPoint[] {clickTouchPoint}));
336 
337         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
338 
339         // Send 3 motion events, leaving the extra down in the queue
340         mMessageCapturingHandler.sendOneMessage();
341         mMessageCapturingHandler.sendOneMessage();
342         mMessageCapturingHandler.sendOneMessage();
343 
344         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
345 
346         verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
347         assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
348         assertThat(mCaptor1.getAllValues().get(1), mIsLineMiddle);
349         assertThat(mCaptor1.getAllValues().get(2), mIsLineEnd);
350         assertThat(mCaptor1.getAllValues().get(3), mIsClickDown);
351         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
352         assertFalse(mMessageCapturingHandler.hasMessages());
353     }
354 
355     @Test
testInjectEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassNew()356     public void testInjectEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassNew()
357             throws RemoteException {
358         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
359         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
360         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
361 
362         injectEventsSync(mClickList, mServiceInterface, CLICK_SEQUENCE);
363         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
364 
365         verify(mServiceInterface, times(1)).onPerformGestureResult(LINE_SEQUENCE, false);
366         verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
367         assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
368         assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
369         assertThat(mCaptor1.getAllValues().get(2), mIsClickDown);
370     }
371 
372     @Test
testInjectEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassNew()373     public void testInjectEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassNew()
374             throws RemoteException {
375         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
376         // Tack a click down to the end of the line
377         TouchPoint clickTouchPoint = new TouchPoint();
378         clickTouchPoint.mIsStartOfPath = true;
379         clickTouchPoint.mX = CLICK_POINT.x;
380         clickTouchPoint.mY = CLICK_POINT.y;
381         mLineList.add(new GestureStep(0, 1, new TouchPoint[] {clickTouchPoint}));
382         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
383 
384         // Send 3 motion events, leaving newEvent in the queue
385         mMessageCapturingHandler.sendOneMessage();
386         mMessageCapturingHandler.sendOneMessage();
387         mMessageCapturingHandler.sendOneMessage();
388 
389         injectEventsSync(mClickList, mServiceInterface, CLICK_SEQUENCE);
390         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
391 
392         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
393         verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
394         assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
395         assertThat(mCaptor1.getAllValues().get(1), mIsLineMiddle);
396         assertThat(mCaptor1.getAllValues().get(2), mIsLineEnd);
397         assertThat(mCaptor1.getAllValues().get(3), mIsClickDown);
398     }
399 
400     @Test
testContinuedGesture_continuationArrivesAfterDispatched_gestureCompletes()401     public void testContinuedGesture_continuationArrivesAfterDispatched_gestureCompletes()
402             throws Exception {
403         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
404         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
405         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
406         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
407         injectEventsSync(mContinuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
408         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
409         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
410         verify(next, times(5)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
411         List<MotionEvent> events = mCaptor1.getAllValues();
412         long downTime = events.get(0).getDownTime();
413         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN,
414                 hasEventTime(downTime)));
415         assertThat(events, everyItem(hasDownTime(downTime)));
416         assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE,
417                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
418         // Timing will restart when the gesture continues
419         long secondSequenceStart = events.get(2).getEventTime();
420         assertTrue(secondSequenceStart >= events.get(1).getEventTime());
421         assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE));
422         assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,
423                 hasEventTime(secondSequenceStart + CONTINUED_LINE_INTERVAL)));
424         assertThat(events.get(4), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_UP,
425                 hasEventTime(secondSequenceStart + CONTINUED_LINE_INTERVAL)));
426     }
427 
428     @Test
testContinuedGesture_withTwoTouchPoints_gestureCompletes()429     public void testContinuedGesture_withTwoTouchPoints_gestureCompletes()
430             throws Exception {
431         // Run one point through the continued line backwards
432         int backLineId1 = 30;
433         int backLineId2 = 30;
434         List<GestureStep> continuedBackLineList1 = createSimpleGestureFromPoints(backLineId1, 0,
435                 true, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_END, CONTINUED_LINE_MID2);
436         List<GestureStep> continuedBackLineList2 = createSimpleGestureFromPoints(backLineId2,
437                 backLineId1, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID2,
438                 CONTINUED_LINE_MID1, CONTINUED_LINE_START);
439         List<GestureStep> combinedLines1 = combineGestureSteps(
440                 mContinuedLineList1, continuedBackLineList1);
441         List<GestureStep> combinedLines2 = combineGestureSteps(
442                 mContinuedLineList2, continuedBackLineList2);
443 
444         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
445         injectEventsSync(combinedLines1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
446         injectEventsSync(combinedLines2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
447         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
448         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
449         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
450         verify(next, times(7)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
451         List<MotionEvent> events = mCaptor1.getAllValues();
452         long downTime = events.get(0).getDownTime();
453         assertThat(events.get(0), allOf(
454                 anyOf(isAtPoint(CONTINUED_LINE_END), isAtPoint(CONTINUED_LINE_START)),
455                 IS_ACTION_DOWN, hasEventTime(downTime)));
456         assertThat(events, everyItem(hasDownTime(downTime)));
457         assertThat(events.get(1), allOf(containsPoints(CONTINUED_LINE_START, CONTINUED_LINE_END),
458                 IS_ACTION_POINTER_DOWN, hasEventTime(downTime)));
459         assertThat(events.get(2), allOf(containsPoints(CONTINUED_LINE_MID1, CONTINUED_LINE_MID2),
460                 IS_ACTION_MOVE, hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
461         assertThat(events.get(3), allOf(containsPoints(CONTINUED_LINE_MID1, CONTINUED_LINE_MID2),
462                 IS_ACTION_MOVE, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 2)));
463         assertThat(events.get(4), allOf(containsPoints(CONTINUED_LINE_START, CONTINUED_LINE_END),
464                 IS_ACTION_MOVE, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
465         assertThat(events.get(5), allOf(containsPoints(CONTINUED_LINE_START, CONTINUED_LINE_END),
466                 IS_ACTION_POINTER_UP, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
467         assertThat(events.get(6), allOf(
468                 anyOf(isAtPoint(CONTINUED_LINE_END), isAtPoint(CONTINUED_LINE_START)),
469                 IS_ACTION_UP, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
470     }
471 
472 
473     @Test
testContinuedGesture_continuationArrivesWhileDispatching_gestureCompletes()474     public void testContinuedGesture_continuationArrivesWhileDispatching_gestureCompletes()
475             throws Exception {
476         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
477         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
478         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
479         injectEventsSync(mContinuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
480         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
481         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
482         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
483         verify(next, times(5)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
484         List<MotionEvent> events = mCaptor1.getAllValues();
485         long downTime = events.get(0).getDownTime();
486         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN,
487                 hasEventTime(downTime)));
488         assertThat(events, everyItem(hasDownTime(downTime)));
489         assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE,
490                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
491         assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE,
492                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 2)));
493         assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,
494                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
495         assertThat(events.get(4), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_UP,
496                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
497     }
498 
499     @Test
testContinuedGesture_twoContinuationsArriveWhileDispatching_gestureCompletes()500     public void testContinuedGesture_twoContinuationsArriveWhileDispatching_gestureCompletes()
501             throws Exception {
502         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
503         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
504         // Continue line again
505         List<GestureStep> continuedLineList2 = createSimpleGestureFromPoints(
506                 CONTINUED_LINE_STROKE_ID_2, CONTINUED_LINE_STROKE_ID_1, true,
507                 CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID1,
508                 CONTINUED_LINE_MID2, CONTINUED_LINE_END);
509         // Finish line by backtracking
510         int strokeId3 = CONTINUED_LINE_STROKE_ID_2 + 1;
511         int sequence3 = CONTINUED_LINE_SEQUENCE_2 + 1;
512         List<GestureStep> continuedLineList3 = createSimpleGestureFromPoints(strokeId3,
513                 CONTINUED_LINE_STROKE_ID_2, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_END,
514                 CONTINUED_LINE_MID2);
515 
516         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
517         injectEventsSync(continuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
518         injectEventsSync(continuedLineList3, mServiceInterface, sequence3);
519         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
520         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
521         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
522         verify(mServiceInterface).onPerformGestureResult(sequence3, true);
523         verify(next, times(6)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
524         List<MotionEvent> events = mCaptor1.getAllValues();
525         long downTime = events.get(0).getDownTime();
526         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN,
527                 hasEventTime(downTime)));
528         assertThat(events, everyItem(hasDownTime(downTime)));
529         assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE,
530                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
531         assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE,
532                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 2)));
533         assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,
534                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
535         assertThat(events.get(4), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE,
536                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 4)));
537         assertThat(events.get(5), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_UP,
538                 hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 4)));
539     }
540 
541     @Test
testContinuedGesture_nonContinuingGestureArrivesDuringDispatch_gestureCanceled()542     public void testContinuedGesture_nonContinuingGestureArrivesDuringDispatch_gestureCanceled()
543             throws Exception {
544         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
545         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
546         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
547         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
548         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
549         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, false);
550         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
551         verify(next, times(5)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
552         List<MotionEvent> events = mCaptor1.getAllValues();
553         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
554         assertThat(events.get(1), IS_ACTION_CANCEL);
555         assertThat(events.get(2), allOf(isAtPoint(LINE_START), IS_ACTION_DOWN));
556         assertThat(events.get(3), allOf(isAtPoint(LINE_END), IS_ACTION_MOVE));
557         assertThat(events.get(4), allOf(isAtPoint(LINE_END), IS_ACTION_UP));
558     }
559 
560     @Test
testContinuedGesture_nonContinuingGestureArrivesAfterDispatch_gestureCanceled()561     public void testContinuedGesture_nonContinuingGestureArrivesAfterDispatch_gestureCanceled()
562             throws Exception {
563         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
564         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
565         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
566         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
567         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
568         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
569         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
570         verify(next, times(6)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
571         List<MotionEvent> events = mCaptor1.getAllValues();
572         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
573         assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE));
574         assertThat(events.get(2), IS_ACTION_CANCEL);
575         assertThat(events.get(3), allOf(isAtPoint(LINE_START), IS_ACTION_DOWN));
576         assertThat(events.get(4), allOf(isAtPoint(LINE_END), IS_ACTION_MOVE));
577         assertThat(events.get(5), allOf(isAtPoint(LINE_END), IS_ACTION_UP));
578     }
579 
580     @Test
testContinuedGesture_misMatchedContinuationArrives_bothGesturesCanceled()581     public void testContinuedGesture_misMatchedContinuationArrives_bothGesturesCanceled()
582             throws Exception {
583         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
584         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
585         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
586         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
587         List<GestureStep> discontinuousGesture = mContinuedLineList2
588                 .subList(1, mContinuedLineList2.size());
589         injectEventsSync(discontinuousGesture, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
590         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
591         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, false);
592         verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
593         List<MotionEvent> events = mCaptor1.getAllValues();
594         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
595         assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE));
596         assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_CANCEL));
597     }
598 
599     @Test
testContinuedGesture_continuationArrivesFromOtherService_bothGesturesCanceled()600     public void testContinuedGesture_continuationArrivesFromOtherService_bothGesturesCanceled()
601             throws Exception {
602         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
603         IAccessibilityServiceClient otherService = mock(IAccessibilityServiceClient.class);
604         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
605         mMessageCapturingHandler.sendOneMessage(); // Send a motion events
606         injectEventsSync(mContinuedLineList2, otherService, CONTINUED_LINE_SEQUENCE_2);
607         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
608         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, false);
609         verify(otherService).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, false);
610         verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
611         List<MotionEvent> events = mCaptor1.getAllValues();
612         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
613         assertThat(events.get(1), IS_ACTION_CANCEL);
614     }
615 
616     @Test
testContinuedGesture_realGestureArrivesInBetween_getsCanceled()617     public void testContinuedGesture_realGestureArrivesInBetween_getsCanceled()
618             throws Exception {
619         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
620         injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
621         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
622         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
623 
624         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
625 
626         injectEventsSync(mContinuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
627         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
628         verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, false);
629         verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
630         List<MotionEvent> events = mCaptor1.getAllValues();
631         assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
632         assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE));
633         assertThat(events.get(2), IS_ACTION_CANCEL);
634         assertThat(events.get(3), allOf(isAtPoint(CLICK_POINT), IS_ACTION_DOWN));
635     }
636 
637     @Test
testClearEvents_realGestureInProgress_shouldForgetAboutGesture()638     public void testClearEvents_realGestureInProgress_shouldForgetAboutGesture() {
639         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
640         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
641         mMotionEventInjector.clearEvents(MOTION_EVENT_SOURCE);
642         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
643         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
644 
645         verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
646         assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
647         assertThat(mCaptor1.getAllValues().get(1), mIsLineStart);
648     }
649 
650     @Test
testClearEventsOnOtherSource_realGestureInProgress_shouldNotForgetAboutGesture()651     public void testClearEventsOnOtherSource_realGestureInProgress_shouldNotForgetAboutGesture() {
652         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
653         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
654         mMotionEventInjector.clearEvents(OTHER_EVENT_SOURCE);
655         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
656         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
657 
658         verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
659         assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
660         assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
661         assertThat(mCaptor1.getAllValues().get(2), mIsLineStart);
662     }
663 
664     @Test
testOnDestroy_shouldCancelGestures()665     public void testOnDestroy_shouldCancelGestures() throws RemoteException {
666         mMotionEventInjector.onDestroy();
667         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
668         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
669     }
670 
671     @Test
testInjectEvents_withNoNext_shouldCancel()672     public void testInjectEvents_withNoNext_shouldCancel() throws RemoteException {
673         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
674         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
675     }
676 
677     @Test
testOnMotionEvent_withNoNext_shouldNotCrash()678     public void testOnMotionEvent_withNoNext_shouldNotCrash() {
679         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
680     }
681 
682     @Test
testOnKeyEvent_shouldPassToNext()683     public void testOnKeyEvent_shouldPassToNext() {
684         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
685         KeyEvent event = new KeyEvent(0, 0);
686         mMotionEventInjector.onKeyEvent(event, 0);
687         verify(next).onKeyEvent(event, 0);
688     }
689 
690     @Test
testOnKeyEvent_withNoNext_shouldNotCrash()691     public void testOnKeyEvent_withNoNext_shouldNotCrash() {
692         KeyEvent event = new KeyEvent(0, 0);
693         mMotionEventInjector.onKeyEvent(event, 0);
694     }
695 
696     @Test
testOnAccessibilityEvent_shouldPassToNext()697     public void testOnAccessibilityEvent_shouldPassToNext() {
698         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
699         AccessibilityEvent event = AccessibilityEvent.obtain();
700         mMotionEventInjector.onAccessibilityEvent(event);
701         verify(next).onAccessibilityEvent(event);
702     }
703 
704     @Test
testOnAccessibilityEvent_withNoNext_shouldNotCrash()705     public void testOnAccessibilityEvent_withNoNext_shouldNotCrash() {
706         AccessibilityEvent event = AccessibilityEvent.obtain();
707         mMotionEventInjector.onAccessibilityEvent(event);
708     }
709 
injectEventsSync(List<GestureStep> gestureSteps, IAccessibilityServiceClient serviceInterface, int sequence)710     private void injectEventsSync(List<GestureStep> gestureSteps,
711             IAccessibilityServiceClient serviceInterface, int sequence) {
712         mMotionEventInjector.injectEvents(gestureSteps, serviceInterface, sequence);
713         // Dispatch the message sent by the injector. Our simple handler doesn't guarantee stuff
714         // happens in order.
715         mMessageCapturingHandler.sendLastMessage();
716     }
717 
createSimpleGestureFromPoints(int strokeId, int continuedStrokeId, boolean continued, long interval, Point... points)718     private List<GestureStep> createSimpleGestureFromPoints(int strokeId, int continuedStrokeId,
719             boolean continued, long interval, Point... points) {
720         List<GestureStep> gesture = new ArrayList<>(points.length);
721         TouchPoint[] touchPoints = new TouchPoint[1];
722         touchPoints[0] = new TouchPoint();
723         for (int i = 0; i < points.length; i++) {
724             touchPoints[0].mX = points[i].x;
725             touchPoints[0].mY = points[i].y;
726             touchPoints[0].mIsStartOfPath = ((i == 0) && (continuedStrokeId <= 0));
727             touchPoints[0].mContinuedStrokeId = continuedStrokeId;
728             touchPoints[0].mStrokeId = strokeId;
729             touchPoints[0].mIsEndOfPath = ((i == points.length - 1) && !continued);
730             gesture.add(new GestureStep(interval * i, 1, touchPoints));
731         }
732         return gesture;
733     }
734 
combineGestureSteps(List<GestureStep> list1, List<GestureStep> list2)735     List<GestureStep> combineGestureSteps(List<GestureStep> list1, List<GestureStep> list2) {
736         assertEquals(list1.size(), list2.size());
737         List<GestureStep> gesture = new ArrayList<>(list1.size());
738         for (int i = 0; i < list1.size(); i++) {
739             int numPoints1 = list1.get(i).numTouchPoints;
740             int numPoints2 = list2.get(i).numTouchPoints;
741             TouchPoint[] touchPoints = new TouchPoint[numPoints1 + numPoints2];
742             for (int j = 0; j < numPoints1; j++) {
743                 touchPoints[j] = new TouchPoint();
744                 touchPoints[j].copyFrom(list1.get(i).touchPoints[j]);
745             }
746             for (int j = 0; j < numPoints2; j++) {
747                 touchPoints[numPoints1 + j] = new TouchPoint();
748                 touchPoints[numPoints1 + j].copyFrom(list2.get(i).touchPoints[j]);
749             }
750             gesture.add(new GestureStep(list1.get(i).timeSinceGestureStart,
751                     numPoints1 + numPoints2, touchPoints));
752         }
753         return gesture;
754     }
755 
attachMockNext(MotionEventInjector motionEventInjector)756     private EventStreamTransformation attachMockNext(MotionEventInjector motionEventInjector) {
757         EventStreamTransformation next = mock(EventStreamTransformation.class);
758         motionEventInjector.setNext(next);
759         return next;
760     }
761 
762     static class MotionEventMatcher extends TypeSafeMatcher<MotionEvent> {
763         long mDownTime;
764         long mEventTime;
765         long mActionMasked;
766         int mX;
767         int mY;
768 
MotionEventMatcher(long downTime, long eventTime, int actionMasked, int x, int y)769         MotionEventMatcher(long downTime, long eventTime, int actionMasked, int x, int y) {
770             mDownTime = downTime;
771             mEventTime = eventTime;
772             mActionMasked = actionMasked;
773             mX = x;
774             mY = y;
775         }
776 
MotionEventMatcher(MotionEvent event)777         MotionEventMatcher(MotionEvent event) {
778             this(event.getDownTime(), event.getEventTime(), event.getActionMasked(),
779                     (int) event.getX(), (int) event.getY());
780         }
781 
offsetTimesBy(long timeOffset)782         void offsetTimesBy(long timeOffset) {
783             mDownTime += timeOffset;
784             mEventTime += timeOffset;
785         }
786 
787         @Override
matchesSafely(MotionEvent event)788         public boolean matchesSafely(MotionEvent event) {
789             if ((event.getDownTime() == mDownTime) && (event.getEventTime() == mEventTime)
790                     && (event.getActionMasked() == mActionMasked) && ((int) event.getX() == mX)
791                     && ((int) event.getY() == mY)) {
792                 return true;
793             }
794             Log.e(LOG_TAG, "MotionEvent match failed");
795             Log.e(LOG_TAG, "event.getDownTime() = " + event.getDownTime()
796                     + ", expected " + mDownTime);
797             Log.e(LOG_TAG, "event.getEventTime() = " + event.getEventTime()
798                     + ", expected " + mEventTime);
799             Log.e(LOG_TAG, "event.getActionMasked() = " + event.getActionMasked()
800                     + ", expected " + mActionMasked);
801             Log.e(LOG_TAG, "event.getX() = " + event.getX() + ", expected " + mX);
802             Log.e(LOG_TAG, "event.getY() = " + event.getY() + ", expected " + mY);
803             return false;
804         }
805 
806         @Override
describeTo(Description description)807         public void describeTo(Description description) {
808             description.appendText("Motion event matcher");
809         }
810     }
811 
812     private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
813         int mAction;
814 
MotionEventActionMatcher(int action)815         MotionEventActionMatcher(int action) {
816             super();
817             mAction = action;
818         }
819 
820         @Override
matchesSafely(MotionEvent motionEvent)821         protected boolean matchesSafely(MotionEvent motionEvent) {
822             return motionEvent.getActionMasked() == mAction;
823         }
824 
825         @Override
describeTo(Description description)826         public void describeTo(Description description) {
827             description.appendText("Matching to action " + mAction);
828         }
829     }
830 
isAtPoint(final Point point)831     private static TypeSafeMatcher<MotionEvent> isAtPoint(final Point point) {
832         return new TypeSafeMatcher<MotionEvent>() {
833             @Override
834             protected boolean matchesSafely(MotionEvent event) {
835                 return ((event.getX() == point.x) && (event.getY() == point.y));
836             }
837 
838             @Override
839             public void describeTo(Description description) {
840                 description.appendText("Is at point " + point);
841             }
842         };
843     }
844 
845     private static TypeSafeMatcher<MotionEvent> containsPoints(final Point... points) {
846         return new TypeSafeMatcher<MotionEvent>() {
847             @Override
848             protected boolean matchesSafely(MotionEvent event) {
849                 MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
850                 for (int i = 0; i < points.length; i++) {
851                     boolean havePoint = false;
852                     for (int j = 0; j < points.length; j++) {
853                         event.getPointerCoords(j, coords);
854                         if ((points[i].x == coords.x) && (points[i].y == coords.y)) {
855                             havePoint = true;
856                         }
857                     }
858                     if (!havePoint) {
859                         return false;
860                     }
861                 }
862                 return true;
863             }
864 
865             @Override
866             public void describeTo(Description description) {
867                 description.appendText("Contains points " + points);
868             }
869         };
870     }
871 
872     private static TypeSafeMatcher<MotionEvent> hasDownTime(final long downTime) {
873         return new TypeSafeMatcher<MotionEvent>() {
874             @Override
875             protected boolean matchesSafely(MotionEvent event) {
876                 return event.getDownTime() == downTime;
877             }
878 
879             @Override
880             public void describeTo(Description description) {
881                 description.appendText("Down time = " + downTime);
882             }
883         };
884     }
885 
886     private static TypeSafeMatcher<MotionEvent> hasEventTime(final long eventTime) {
887         return new TypeSafeMatcher<MotionEvent>() {
888             @Override
889             protected boolean matchesSafely(MotionEvent event) {
890                 return event.getEventTime() == eventTime;
891             }
892 
893             @Override
894             public void describeTo(Description description) {
895                 description.appendText("Event time = " + eventTime);
896             }
897         };
898     }
899 
900     private static TypeSafeMatcher<MotionEvent> hasTimeFromDown(final long timeFromDown) {
901         return new TypeSafeMatcher<MotionEvent>() {
902             @Override
903             protected boolean matchesSafely(MotionEvent event) {
904                 return (event.getEventTime() - event.getDownTime()) == timeFromDown;
905             }
906 
907             @Override
908             public void describeTo(Description description) {
909                 description.appendText("Time from down to event times = " + timeFromDown);
910             }
911         };
912     }
913 
914     private static TypeSafeMatcher<MotionEvent> hasStandardInitialization() {
915         return new TypeSafeMatcher<MotionEvent>() {
916             @Override
917             protected boolean matchesSafely(MotionEvent event) {
918                 return (0 == event.getActionIndex()) && (0 == event.getDeviceId())
919                         && (0 == event.getEdgeFlags()) && (0 == event.getFlags())
920                         && (0 == event.getMetaState()) && (0F == event.getOrientation())
921                         && (0F == event.getTouchMajor()) && (0F == event.getTouchMinor())
922                         && (1F == event.getXPrecision()) && (1F == event.getYPrecision())
923                         && (1 == event.getPointerCount()) && (1F == event.getPressure())
924                         && (InputDevice.SOURCE_TOUCHSCREEN == event.getSource());
925             }
926 
927             @Override
928             public void describeTo(Description description) {
929                 description.appendText("Has standard values for all parameters");
930             }
931         };
932     }
933 }
934