MotionEventInjectorTest.java revision 89e3ffc66c5a05f188ff9748b48abebc247f664b
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 org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertTrue;
22import static org.mockito.Matchers.anyInt;
23import static org.mockito.Matchers.argThat;
24import static org.mockito.Matchers.eq;
25import static org.mockito.Mockito.mock;
26import static org.mockito.Mockito.reset;
27import static org.mockito.Mockito.times;
28import static org.mockito.Mockito.verify;
29import static org.mockito.Mockito.verifyNoMoreInteractions;
30import static org.mockito.Mockito.verifyZeroInteractions;
31
32import android.accessibilityservice.IAccessibilityServiceClient;
33import android.os.Handler;
34import android.os.Looper;
35import android.os.Message;
36import android.os.RemoteException;
37import android.support.test.runner.AndroidJUnit4;
38import android.util.Log;
39import android.util.Pair;
40import android.view.InputDevice;
41import android.view.KeyEvent;
42import android.view.MotionEvent;
43import android.view.WindowManagerPolicy;
44import java.util.ArrayList;
45import java.util.List;
46
47import android.view.accessibility.AccessibilityEvent;
48import org.junit.Before;
49import org.junit.BeforeClass;
50import org.junit.Test;
51import org.junit.runner.RunWith;
52import org.mockito.ArgumentCaptor;
53import org.mockito.ArgumentMatcher;
54
55/**
56 * Tests for MotionEventInjector
57 */
58@RunWith(AndroidJUnit4.class)
59public class MotionEventInjectorTest {
60    private static final String LOG_TAG = "MotionEventInjectorTest";
61    private static final int CLICK_X = 100;
62    private static final int CLICK_Y_START = 200;
63    private static final int CLICK_Y_END = 201;
64    private static final int CLICK_DURATION = 10;
65    private static final int SEQUENCE = 50;
66
67    private static final int SECOND_CLICK_X = 1000;
68    private static final int SECOND_CLICK_Y = 2000;
69    private static final int SECOND_SEQUENCE = 51;
70
71    private static final int MOTION_EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
72    private static final int OTHER_EVENT_SOURCE = InputDevice.SOURCE_MOUSE;
73
74    MotionEventInjector mMotionEventInjector;
75    IAccessibilityServiceClient mServiceInterface;
76    List<MotionEvent> mClickList = new ArrayList<>();
77    List<MotionEvent> mSecondClickList = new ArrayList<>();
78    ArgumentCaptor<MotionEvent> mCaptor1 = ArgumentCaptor.forClass(MotionEvent.class);
79    ArgumentCaptor<MotionEvent> mCaptor2 = ArgumentCaptor.forClass(MotionEvent.class);
80    MessageCapturingHandler mMessageCapturingHandler;
81    MotionEventMatcher mClickEvent0Matcher;
82    MotionEventMatcher mClickEvent1Matcher;
83    MotionEventMatcher mClickEvent2Matcher;
84    MotionEventMatcher mSecondClickEvent0Matcher;
85
86    @BeforeClass
87    public static void oneTimeInitialization() {
88        if (Looper.myLooper() == null) {
89            Looper.prepare();
90        }
91    }
92
93    @Before
94    public void setUp() {
95        mMessageCapturingHandler = new MessageCapturingHandler(new Handler.Callback() {
96            @Override
97            public boolean handleMessage(Message msg) {
98                return mMotionEventInjector.handleMessage(msg);
99            }
100        });
101        mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
102        mClickList.add(
103                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
104        mClickList.add(MotionEvent.obtain(
105                0, CLICK_DURATION, MotionEvent.ACTION_MOVE, CLICK_X, CLICK_Y_END, 0));
106        mClickList.add(MotionEvent.obtain(
107                0, CLICK_DURATION, MotionEvent.ACTION_UP, CLICK_X, CLICK_Y_END, 0));
108        for (int i = 0; i < mClickList.size(); i++) {
109            mClickList.get(i).setSource(MOTION_EVENT_SOURCE);
110        }
111
112        mClickEvent0Matcher = new MotionEventMatcher(mClickList.get(0));
113        mClickEvent1Matcher = new MotionEventMatcher(mClickList.get(1));
114        mClickEvent2Matcher = new MotionEventMatcher(mClickList.get(2));
115
116        mSecondClickList.add(MotionEvent.obtain(
117                0, 0, MotionEvent.ACTION_DOWN, SECOND_CLICK_X, SECOND_CLICK_Y, 0));
118        mSecondClickList.add(MotionEvent.obtain(
119                0, CLICK_DURATION, MotionEvent.ACTION_MOVE, SECOND_CLICK_X, CLICK_Y_END, 0));
120        mSecondClickList.add(MotionEvent.obtain(
121                0, CLICK_DURATION, MotionEvent.ACTION_UP, SECOND_CLICK_X, CLICK_Y_END, 0));
122        for (int i = 0; i < mSecondClickList.size(); i++) {
123            mSecondClickList.get(i).setSource(MOTION_EVENT_SOURCE);
124        }
125
126        mSecondClickEvent0Matcher = new MotionEventMatcher(mSecondClickList.get(0));
127
128        mServiceInterface = mock(IAccessibilityServiceClient.class);
129    }
130
131    @Test
132    public void testInjectEvents_shouldEmergeInOrderWithCorrectTiming() throws RemoteException {
133        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
134        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
135        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
136        verifyNoMoreInteractions(next);
137        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
138
139        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
140                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
141        long gestureStart = mCaptor1.getValue().getDownTime();
142        mClickEvent0Matcher.offsetTimesBy(gestureStart);
143        mClickEvent1Matcher.offsetTimesBy(gestureStart);
144        mClickEvent2Matcher.offsetTimesBy(gestureStart);
145
146        verify(next).onMotionEvent(argThat(mClickEvent0Matcher), argThat(mClickEvent0Matcher),
147                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
148        verifyNoMoreInteractions(next);
149        reset(next);
150
151        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
152        verify(next).onMotionEvent(argThat(mClickEvent1Matcher), argThat(mClickEvent1Matcher),
153                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
154        verifyNoMoreInteractions(next);
155        reset(next);
156
157        verifyZeroInteractions(mServiceInterface);
158
159        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
160        verify(next).onMotionEvent(argThat(mClickEvent2Matcher), argThat(mClickEvent2Matcher),
161                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
162        verifyNoMoreInteractions(next);
163        reset(next);
164
165        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, true);
166        verifyNoMoreInteractions(mServiceInterface);
167    }
168
169    @Test
170    public void testInjectEvents_eventWithManyPointers_shouldNotCrash() {
171        int manyPointersCount = 20;
172        MotionEvent.PointerCoords[] pointerCoords =
173                new MotionEvent.PointerCoords[manyPointersCount];
174        MotionEvent.PointerProperties[] pointerProperties =
175                new MotionEvent.PointerProperties[manyPointersCount];
176        for (int i = 0; i < manyPointersCount; i++) {
177            pointerProperties[i] = new MotionEvent.PointerProperties();
178            pointerProperties[i].id = i;
179            pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
180            pointerCoords[i] = new MotionEvent.PointerCoords();
181            pointerCoords[i].clear();
182            pointerCoords[i].pressure = 1.0f;
183            pointerCoords[i].size = 1.0f;
184            pointerCoords[i].x = i;
185            pointerCoords[i].y = i;
186        }
187        List<MotionEvent> events = new ArrayList<>();
188        events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, manyPointersCount,
189                pointerProperties, pointerCoords, 0, 0,
190                1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0));
191        events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, manyPointersCount,
192                pointerProperties, pointerCoords, 0, 0,
193                1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0));
194        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
195        mMotionEventInjector.injectEvents(events, mServiceInterface, SEQUENCE);
196        mMessageCapturingHandler.sendAllMessages();
197        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
198        assertEquals(MotionEvent.ACTION_DOWN, mCaptor1.getAllValues().get(0).getActionMasked());
199        assertEquals(MotionEvent.ACTION_UP, mCaptor1.getAllValues().get(1).getActionMasked());
200    }
201
202    @Test
203    public void testRegularEvent_afterGestureComplete_shouldPassToNext() {
204        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
205        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
206        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
207        mMessageCapturingHandler.sendAllMessages(); // Send all motion events
208        reset(next);
209        mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mClickList.get(0), 0);
210        verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
211                argThat(mClickEvent0Matcher), eq(0));
212    }
213
214    @Test
215    public void testInjectEvents_withRealGestureUnderway_shouldCancelRealAndPassInjected() {
216        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
217        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
218        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
219        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
220
221        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
222        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
223        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
224        reset(next);
225
226        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
227        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
228                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
229        long gestureStart = mCaptor1.getValue().getDownTime();
230        mSecondClickEvent0Matcher.offsetTimesBy(gestureStart);
231
232        verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
233                argThat(mSecondClickEvent0Matcher), eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
234    }
235
236    @Test
237    public void testInjectEvents_withRealMouseGestureUnderway_shouldContinueRealAndPassInjected() {
238        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
239        MotionEvent mouseEvent = MotionEvent.obtain(mClickList.get(0));
240        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
241        MotionEventMatcher mouseEventMatcher = new MotionEventMatcher(mouseEvent);
242        mMotionEventInjector.onMotionEvent(mouseEvent, mouseEvent, 0);
243        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
244        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
245
246        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
247        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
248        assertTrue(mouseEventMatcher.matches(mCaptor1.getAllValues().get(0)));
249        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(1).getDownTime());
250        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(1)));
251    }
252
253    @Test
254    public void testInjectEvents_withRealGestureFinished_shouldJustPassInjected() {
255        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
256        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
257        mMotionEventInjector.onMotionEvent(mClickList.get(1), mClickList.get(1), 0);
258        mMotionEventInjector.onMotionEvent(mClickList.get(2), mClickList.get(2), 0);
259
260        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
261        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
262        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
263
264        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
265        assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
266        assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
267        reset(next);
268
269        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
270        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
271                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
272        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getValue().getDownTime());
273        verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
274                argThat(mSecondClickEvent0Matcher), eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
275    }
276
277    @Test
278    public void testOnMotionEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassReal()
279            throws RemoteException {
280        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
281        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
282        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
283
284        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
285        mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mSecondClickList.get(0), 0);
286
287        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
288        mClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(0).getDownTime());
289        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
290        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
291        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
292        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
293    }
294
295    @Test
296    public void testOnMotionEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassReal()
297            throws RemoteException {
298        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
299        mClickList.add(MotionEvent.obtain(2 * CLICK_DURATION, 2 * CLICK_DURATION,
300                MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
301        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
302        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
303
304        // Send 3 motion events, leaving the extra down in the queue
305        mMessageCapturingHandler.sendOneMessage();
306        mMessageCapturingHandler.sendOneMessage();
307        mMessageCapturingHandler.sendOneMessage();
308
309        mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mClickList.get(0), 0);
310
311        verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
312        long gestureStart = mCaptor1.getAllValues().get(0).getDownTime();
313        mClickEvent0Matcher.offsetTimesBy(gestureStart);
314        mClickEvent1Matcher.offsetTimesBy(gestureStart);
315        mClickEvent2Matcher.offsetTimesBy(gestureStart);
316        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
317        assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
318        assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
319        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(3)));
320
321        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
322        assertFalse(mMessageCapturingHandler.hasMessages());
323    }
324
325    @Test
326    public void testInjectEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassNew()
327            throws RemoteException {
328        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
329        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
330        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
331        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
332
333        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
334        mMessageCapturingHandler.sendLastMessage(); // Process the second event injection
335        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
336
337        verify(mServiceInterface, times(1)).onPerformGestureResult(SEQUENCE, false);
338        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
339        mClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(0).getDownTime());
340        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
341        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
342        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(2).getDownTime());
343        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
344    }
345
346    @Test
347    public void testInjectEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassNew()
348            throws RemoteException {
349        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
350        MotionEvent newEvent = MotionEvent.obtain(2 * CLICK_DURATION, 2 * CLICK_DURATION,
351                MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0);
352        newEvent.setSource(mClickList.get(0).getSource());
353        mClickList.add(newEvent);
354        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
355        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
356
357        // Send 3 motion events, leaving newEvent in the queue
358        mMessageCapturingHandler.sendOneMessage();
359        mMessageCapturingHandler.sendOneMessage();
360        mMessageCapturingHandler.sendOneMessage();
361
362        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
363        mMessageCapturingHandler.sendLastMessage(); // Process the event injection
364        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
365
366        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
367        verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
368        long gestureStart = mCaptor1.getAllValues().get(0).getDownTime();
369        mClickEvent0Matcher.offsetTimesBy(gestureStart);
370        mClickEvent1Matcher.offsetTimesBy(gestureStart);
371        mClickEvent2Matcher.offsetTimesBy(gestureStart);
372        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
373        assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
374        assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
375        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(3).getDownTime());
376        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(3)));
377    }
378
379    @Test
380    public void testClearEvents_realGestureInProgress_shouldForgetAboutGesture() {
381        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
382        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
383        mMotionEventInjector.clearEvents(MOTION_EVENT_SOURCE);
384        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
385        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
386        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
387
388        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
389        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
390        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(1).getDownTime());
391        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(1)));
392    }
393
394    @Test
395    public void testClearEventsOnOtherSource_realGestureInProgress_shouldNotForgetAboutGesture() {
396        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
397        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
398        mMotionEventInjector.clearEvents(OTHER_EVENT_SOURCE);
399        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
400        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
401        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
402
403        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
404        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
405        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
406        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(2).getDownTime());
407        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
408    }
409
410    @Test
411    public void testOnDestroy_shouldCancelGestures() throws RemoteException {
412        mMotionEventInjector.onDestroy();
413        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
414        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
415        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
416    }
417
418    @Test
419    public void testInjectEvents_withNoNext_shouldCancel() throws RemoteException {
420        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
421        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
422        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
423    }
424
425    @Test
426    public void testOnMotionEvent_withNoNext_shouldNotCrash() {
427        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
428    }
429
430    @Test
431    public void testOnKeyEvent_shouldPassToNext() {
432        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
433        KeyEvent event = new KeyEvent(0, 0);
434        mMotionEventInjector.onKeyEvent(event, 0);
435        verify(next).onKeyEvent(event, 0);
436    }
437
438    @Test
439    public void testOnKeyEvent_withNoNext_shouldNotCrash() {
440        KeyEvent event = new KeyEvent(0, 0);
441        mMotionEventInjector.onKeyEvent(event, 0);
442    }
443
444    @Test
445    public void testOnAccessibilityEvent_shouldPassToNext() {
446        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
447        AccessibilityEvent event = AccessibilityEvent.obtain();
448        mMotionEventInjector.onAccessibilityEvent(event);
449        verify(next).onAccessibilityEvent(event);
450    }
451
452    @Test
453    public void testOnAccessibilityEvent_withNoNext_shouldNotCrash() {
454        AccessibilityEvent event = AccessibilityEvent.obtain();
455        mMotionEventInjector.onAccessibilityEvent(event);
456    }
457
458    private EventStreamTransformation attachMockNext(MotionEventInjector motionEventInjector) {
459        EventStreamTransformation next = mock(EventStreamTransformation.class);
460        motionEventInjector.setNext(next);
461        return next;
462    }
463
464    static class MotionEventMatcher extends ArgumentMatcher<MotionEvent> {
465        long mDownTime;
466        long mEventTime;
467        long mActionMasked;
468        int mX;
469        int mY;
470
471        MotionEventMatcher(long downTime, long eventTime, int actionMasked, int x, int y) {
472            mDownTime = downTime;
473            mEventTime = eventTime;
474            mActionMasked = actionMasked;
475            mX = x;
476            mY = y;
477        }
478
479        MotionEventMatcher(MotionEvent event) {
480            this(event.getDownTime(), event.getEventTime(), event.getActionMasked(),
481                    (int) event.getX(), (int) event.getY());
482        }
483
484        void offsetTimesBy(long timeOffset) {
485            mDownTime += timeOffset;
486            mEventTime += timeOffset;
487        }
488
489        @Override
490        public boolean matches(Object o) {
491            MotionEvent event = (MotionEvent) o;
492            if ((event.getDownTime() == mDownTime) && (event.getEventTime() == mEventTime)
493                    && (event.getActionMasked() == mActionMasked) && ((int) event.getX() == mX)
494                    && ((int) event.getY() == mY)) {
495                return true;
496            }
497            Log.e(LOG_TAG, "MotionEvent match failed");
498            Log.e(LOG_TAG, "event.getDownTime() = " + event.getDownTime()
499                    + ", expected " + mDownTime);
500            Log.e(LOG_TAG, "event.getEventTime() = " + event.getEventTime()
501                    + ", expected " + mEventTime);
502            Log.e(LOG_TAG, "event.getActionMasked() = " + event.getActionMasked()
503                    + ", expected " + mActionMasked);
504            Log.e(LOG_TAG, "event.getX() = " + event.getX() + ", expected " + mX);
505            Log.e(LOG_TAG, "event.getY() = " + event.getY() + ", expected " + mY);
506            return false;
507        }
508    }
509}
510