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