MotionEventInjectorTest.java revision a7dcedc65fbdae3625f25e1a40756d251a5a586b
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();
96        mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
97        mClickList.add(
98                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
99        mClickList.add(MotionEvent.obtain(
100                0, CLICK_DURATION, MotionEvent.ACTION_MOVE, CLICK_X, CLICK_Y_END, 0));
101        mClickList.add(MotionEvent.obtain(
102                0, CLICK_DURATION, MotionEvent.ACTION_UP, CLICK_X, CLICK_Y_END, 0));
103        for (int i = 0; i < mClickList.size(); i++) {
104            mClickList.get(i).setSource(MOTION_EVENT_SOURCE);
105        }
106
107        mClickEvent0Matcher = new MotionEventMatcher(mClickList.get(0));
108        mClickEvent1Matcher = new MotionEventMatcher(mClickList.get(1));
109        mClickEvent2Matcher = new MotionEventMatcher(mClickList.get(2));
110
111        mSecondClickList.add(MotionEvent.obtain(
112                0, 0, MotionEvent.ACTION_DOWN, SECOND_CLICK_X, SECOND_CLICK_Y, 0));
113        mSecondClickList.add(MotionEvent.obtain(
114                0, CLICK_DURATION, MotionEvent.ACTION_MOVE, SECOND_CLICK_X, CLICK_Y_END, 0));
115        mSecondClickList.add(MotionEvent.obtain(
116                0, CLICK_DURATION, MotionEvent.ACTION_UP, SECOND_CLICK_X, CLICK_Y_END, 0));
117        for (int i = 0; i < mSecondClickList.size(); i++) {
118            mSecondClickList.get(i).setSource(MOTION_EVENT_SOURCE);
119        }
120
121        mSecondClickEvent0Matcher = new MotionEventMatcher(mSecondClickList.get(0));
122
123        mServiceInterface = mock(IAccessibilityServiceClient.class);
124    }
125
126    @Test
127    public void testInjectEvents_shouldEmergeInOrderWithCorrectTiming() throws RemoteException {
128        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
129        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
130        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
131        verifyNoMoreInteractions(next);
132        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
133
134        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
135                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
136        long gestureStart = mCaptor1.getValue().getDownTime();
137        mClickEvent0Matcher.offsetTimesBy(gestureStart);
138        mClickEvent1Matcher.offsetTimesBy(gestureStart);
139        mClickEvent2Matcher.offsetTimesBy(gestureStart);
140
141        verify(next).onMotionEvent(argThat(mClickEvent0Matcher), argThat(mClickEvent0Matcher),
142                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
143        verifyNoMoreInteractions(next);
144        reset(next);
145
146        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
147        verify(next).onMotionEvent(argThat(mClickEvent1Matcher), argThat(mClickEvent1Matcher),
148                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
149        verifyNoMoreInteractions(next);
150        reset(next);
151
152        verifyZeroInteractions(mServiceInterface);
153
154        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
155        verify(next).onMotionEvent(argThat(mClickEvent2Matcher), argThat(mClickEvent2Matcher),
156                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
157        verifyNoMoreInteractions(next);
158        reset(next);
159
160        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, true);
161        verifyNoMoreInteractions(mServiceInterface);
162    }
163
164    @Test
165    public void testInjectEvents_eventWithManyPointers_shouldNotCrash() {
166        int manyPointersCount = 20;
167        MotionEvent.PointerCoords[] pointerCoords =
168                new MotionEvent.PointerCoords[manyPointersCount];
169        MotionEvent.PointerProperties[] pointerProperties =
170                new MotionEvent.PointerProperties[manyPointersCount];
171        for (int i = 0; i < manyPointersCount; i++) {
172            pointerProperties[i] = new MotionEvent.PointerProperties();
173            pointerProperties[i].id = i;
174            pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
175            pointerCoords[i] = new MotionEvent.PointerCoords();
176            pointerCoords[i].clear();
177            pointerCoords[i].pressure = 1.0f;
178            pointerCoords[i].size = 1.0f;
179            pointerCoords[i].x = i;
180            pointerCoords[i].y = i;
181        }
182        List<MotionEvent> events = new ArrayList<>();
183        events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, manyPointersCount,
184                pointerProperties, pointerCoords, 0, 0,
185                1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0));
186        events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, manyPointersCount,
187                pointerProperties, pointerCoords, 0, 0,
188                1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0));
189        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
190        mMotionEventInjector.injectEvents(events, mServiceInterface, SEQUENCE);
191        mMessageCapturingHandler.sendAllMessages();
192        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
193        assertEquals(MotionEvent.ACTION_DOWN, mCaptor1.getAllValues().get(0).getActionMasked());
194        assertEquals(MotionEvent.ACTION_UP, mCaptor1.getAllValues().get(1).getActionMasked());
195    }
196
197    @Test
198    public void testRegularEvent_afterGestureComplete_shouldPassToNext() {
199        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
200        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
201        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
202        mMessageCapturingHandler.sendAllMessages(); // Send all motion events
203        reset(next);
204        mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mClickList.get(0), 0);
205        verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
206                argThat(mClickEvent0Matcher), eq(0));
207    }
208
209    @Test
210    public void testInjectEvents_withRealGestureUnderway_shouldCancelRealAndPassInjected() {
211        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
212        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
213        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
214        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
215
216        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
217        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
218        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
219        reset(next);
220
221        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
222        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
223                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
224        long gestureStart = mCaptor1.getValue().getDownTime();
225        mSecondClickEvent0Matcher.offsetTimesBy(gestureStart);
226
227        verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
228                argThat(mSecondClickEvent0Matcher), eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
229    }
230
231    @Test
232    public void testInjectEvents_withRealMouseGestureUnderway_shouldContinueRealAndPassInjected() {
233        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
234        MotionEvent mouseEvent = MotionEvent.obtain(mClickList.get(0));
235        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
236        MotionEventMatcher mouseEventMatcher = new MotionEventMatcher(mouseEvent);
237        mMotionEventInjector.onMotionEvent(mouseEvent, mouseEvent, 0);
238        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
239        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
240
241        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
242        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
243        assertTrue(mouseEventMatcher.matches(mCaptor1.getAllValues().get(0)));
244        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(1).getDownTime());
245        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(1)));
246    }
247
248    @Test
249    public void testInjectEvents_withRealGestureFinished_shouldJustPassInjected() {
250        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
251        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
252        mMotionEventInjector.onMotionEvent(mClickList.get(1), mClickList.get(1), 0);
253        mMotionEventInjector.onMotionEvent(mClickList.get(2), mClickList.get(2), 0);
254
255        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
256        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
257        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
258
259        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
260        assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
261        assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
262        reset(next);
263
264        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
265        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
266                eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
267        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getValue().getDownTime());
268        verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
269                argThat(mSecondClickEvent0Matcher), eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
270    }
271
272    @Test
273    public void testOnMotionEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassReal()
274            throws RemoteException {
275        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
276        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
277        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
278
279        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
280        mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mSecondClickList.get(0), 0);
281
282        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
283        mClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(0).getDownTime());
284        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
285        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
286        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
287        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
288    }
289
290    @Test
291    public void testOnMotionEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassReal()
292            throws RemoteException {
293        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
294        mClickList.add(MotionEvent.obtain(2 * CLICK_DURATION, 2 * CLICK_DURATION,
295                MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
296        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
297        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
298
299        // Send 3 motion events, leaving the extra down in the queue
300        mMessageCapturingHandler.sendOneMessage();
301        mMessageCapturingHandler.sendOneMessage();
302        mMessageCapturingHandler.sendOneMessage();
303
304        mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mClickList.get(0), 0);
305
306        verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
307        long gestureStart = mCaptor1.getAllValues().get(0).getDownTime();
308        mClickEvent0Matcher.offsetTimesBy(gestureStart);
309        mClickEvent1Matcher.offsetTimesBy(gestureStart);
310        mClickEvent2Matcher.offsetTimesBy(gestureStart);
311        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
312        assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
313        assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
314        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(3)));
315
316        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
317        assertFalse(mMessageCapturingHandler.hasMessages());
318    }
319
320    @Test
321    public void testInjectEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassNew()
322            throws RemoteException {
323        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
324        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
325        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
326        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
327
328        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
329        mMessageCapturingHandler.sendLastMessage(); // Process the second event injection
330        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
331
332        verify(mServiceInterface, times(1)).onPerformGestureResult(SEQUENCE, false);
333        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
334        mClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(0).getDownTime());
335        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
336        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
337        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(2).getDownTime());
338        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
339    }
340
341    @Test
342    public void testInjectEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassNew()
343            throws RemoteException {
344        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
345        MotionEvent newEvent = MotionEvent.obtain(2 * CLICK_DURATION, 2 * CLICK_DURATION,
346                MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0);
347        newEvent.setSource(mClickList.get(0).getSource());
348        mClickList.add(newEvent);
349        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
350        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
351
352        // Send 3 motion events, leaving newEvent in the queue
353        mMessageCapturingHandler.sendOneMessage();
354        mMessageCapturingHandler.sendOneMessage();
355        mMessageCapturingHandler.sendOneMessage();
356
357        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
358        mMessageCapturingHandler.sendLastMessage(); // Process the event injection
359        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
360
361        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
362        verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
363        long gestureStart = mCaptor1.getAllValues().get(0).getDownTime();
364        mClickEvent0Matcher.offsetTimesBy(gestureStart);
365        mClickEvent1Matcher.offsetTimesBy(gestureStart);
366        mClickEvent2Matcher.offsetTimesBy(gestureStart);
367        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
368        assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
369        assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
370        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(3).getDownTime());
371        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(3)));
372    }
373
374    @Test
375    public void testClearEvents_realGestureInProgress_shouldForgetAboutGesture() {
376        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
377        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
378        mMotionEventInjector.clearEvents(MOTION_EVENT_SOURCE);
379        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
380        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
381        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
382
383        verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
384        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
385        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(1).getDownTime());
386        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(1)));
387    }
388
389    @Test
390    public void testClearEventsOnOtherSource_realGestureInProgress_shouldNotForgetAboutGesture() {
391        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
392        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
393        mMotionEventInjector.clearEvents(OTHER_EVENT_SOURCE);
394        mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
395        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
396        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
397
398        verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
399        assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
400        assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
401        mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(2).getDownTime());
402        assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
403    }
404
405    @Test
406    public void testOnDestroy_shouldCancelGestures() throws RemoteException {
407        mMotionEventInjector.onDestroy();
408        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
409        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
410        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
411    }
412
413    @Test
414    public void testInjectEvents_withNoNext_shouldCancel() throws RemoteException {
415        mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
416        mMessageCapturingHandler.sendOneMessage(); // Process the event injection
417        verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
418    }
419
420    @Test
421    public void testOnMotionEvent_withNoNext_shouldNotCrash() {
422        mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
423    }
424
425    @Test
426    public void testOnKeyEvent_shouldPassToNext() {
427        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
428        KeyEvent event = new KeyEvent(0, 0);
429        mMotionEventInjector.onKeyEvent(event, 0);
430        verify(next).onKeyEvent(event, 0);
431    }
432
433    @Test
434    public void testOnKeyEvent_withNoNext_shouldNotCrash() {
435        KeyEvent event = new KeyEvent(0, 0);
436        mMotionEventInjector.onKeyEvent(event, 0);
437    }
438
439    @Test
440    public void testOnAccessibilityEvent_shouldPassToNext() {
441        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
442        AccessibilityEvent event = AccessibilityEvent.obtain();
443        mMotionEventInjector.onAccessibilityEvent(event);
444        verify(next).onAccessibilityEvent(event);
445    }
446
447    @Test
448    public void testOnAccessibilityEvent_withNoNext_shouldNotCrash() {
449        AccessibilityEvent event = AccessibilityEvent.obtain();
450        mMotionEventInjector.onAccessibilityEvent(event);
451    }
452
453    private EventStreamTransformation attachMockNext(MotionEventInjector motionEventInjector) {
454        EventStreamTransformation next = mock(EventStreamTransformation.class);
455        motionEventInjector.setNext(next);
456        return next;
457    }
458
459    static class MotionEventMatcher extends ArgumentMatcher<MotionEvent> {
460        long mDownTime;
461        long mEventTime;
462        long mActionMasked;
463        int mX;
464        int mY;
465
466        MotionEventMatcher(long downTime, long eventTime, int actionMasked, int x, int y) {
467            mDownTime = downTime;
468            mEventTime = eventTime;
469            mActionMasked = actionMasked;
470            mX = x;
471            mY = y;
472        }
473
474        MotionEventMatcher(MotionEvent event) {
475            this(event.getDownTime(), event.getEventTime(), event.getActionMasked(),
476                    (int) event.getX(), (int) event.getY());
477        }
478
479        void offsetTimesBy(long timeOffset) {
480            mDownTime += timeOffset;
481            mEventTime += timeOffset;
482        }
483
484        @Override
485        public boolean matches(Object o) {
486            MotionEvent event = (MotionEvent) o;
487            if ((event.getDownTime() == mDownTime) && (event.getEventTime() == mEventTime)
488                    && (event.getActionMasked() == mActionMasked) && ((int) event.getX() == mX)
489                    && ((int) event.getY() == mY)) {
490                return true;
491            }
492            Log.e(LOG_TAG, "MotionEvent match failed");
493            Log.e(LOG_TAG, "event.getDownTime() = " + event.getDownTime()
494                    + ", expected " + mDownTime);
495            Log.e(LOG_TAG, "event.getEventTime() = " + event.getEventTime()
496                    + ", expected " + mEventTime);
497            Log.e(LOG_TAG, "event.getActionMasked() = " + event.getActionMasked()
498                    + ", expected " + mActionMasked);
499            Log.e(LOG_TAG, "event.getX() = " + event.getX() + ", expected " + mX);
500            Log.e(LOG_TAG, "event.getY() = " + event.getY() + ", expected " + mY);
501            return false;
502        }
503    }
504
505    private class MessageCapturingHandler extends Handler {
506        List<Pair<Message, Long>> timedMessages = new ArrayList<>();
507
508        @Override
509        public boolean sendMessageAtTime(Message message, long uptimeMillis) {
510            timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
511            return super.sendMessageAtTime(message, uptimeMillis);
512        }
513
514        void sendOneMessage() {
515            Message message = timedMessages.remove(0).first;
516            removeMessages(message.what, message.obj);
517            mMotionEventInjector.handleMessage(message);
518            removeStaleMessages();
519        }
520
521        void sendAllMessages() {
522            while (!timedMessages.isEmpty()) {
523                sendOneMessage();
524            }
525        }
526
527        void sendLastMessage() {
528            Message message = timedMessages.remove(timedMessages.size() - 1).first;
529            removeMessages(message.what, message.obj);
530            mMotionEventInjector.handleMessage(message);
531            removeStaleMessages();
532        }
533
534        boolean hasMessages() {
535            removeStaleMessages();
536            return !timedMessages.isEmpty();
537        }
538
539        private void removeStaleMessages() {
540            for (int i = 0; i < timedMessages.size(); i++) {
541                Message message = timedMessages.get(i).first;
542                if (!hasMessages(message.what, message.obj)) {
543                    timedMessages.remove(i--);
544                }
545            }
546        }
547    }
548}
549