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