1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.test.uiautomator;
18 
19 import android.app.Service;
20 import android.app.UiAutomation;
21 import android.app.UiAutomation.AccessibilityEventFilter;
22 import android.graphics.Point;
23 import android.os.PowerManager;
24 import android.os.RemoteException;
25 import android.os.SystemClock;
26 import android.util.Log;
27 import android.view.InputDevice;
28 import android.view.InputEvent;
29 import android.view.KeyCharacterMap;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.MotionEvent.PointerCoords;
33 import android.view.MotionEvent.PointerProperties;
34 import android.view.ViewConfiguration;
35 import android.view.accessibility.AccessibilityEvent;
36 
37 import java.util.HashMap;
38 import java.util.Map;
39 import java.util.concurrent.TimeoutException;
40 
41 /**
42  * The InteractionProvider is responsible for injecting user events such as touch events
43  * (includes swipes) and text key events into the system. To do so, all it needs to know about
44  * are coordinates of the touch events and text for the text input events.
45  * The InteractionController performs no synchronization. It will fire touch and text input events
46  * as fast as it receives them. All idle synchronization is performed prior to querying the
47  * hierarchy. See {@link QueryController}
48  */
49 class InteractionController {
50 
51     private static final String TAG = InteractionController.class.getSimpleName();
52 
53     // Duration of a long press (with multiplier to ensure detection).
54     private static final long LONG_PRESS_DURATION_MS =
55             (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
56 
57     private final UiDevice mDevice;
58 
59     private static final long REGULAR_CLICK_LENGTH = 100;
60 
61     private long mDownTime;
62 
63     // Inserted after each motion event injection.
64     private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
65 
66     private static final Map<Integer, Integer> KEY_MODIFIER = new HashMap<>();
67 
68     static {
KEY_MODIFIER.put(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON)69         KEY_MODIFIER.put(KeyEvent.KEYCODE_SHIFT_LEFT,
70                 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_SHIFT_RIGHT, KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SHIFT_ON)71         KEY_MODIFIER.put(KeyEvent.KEYCODE_SHIFT_RIGHT,
72                 KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SHIFT_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_ON)73         KEY_MODIFIER.put(KeyEvent.KEYCODE_ALT_LEFT,
74                 KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_ALT_RIGHT, KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_ALT_ON)75         KEY_MODIFIER.put(KeyEvent.KEYCODE_ALT_RIGHT,
76                 KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_ALT_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_SYM, KeyEvent.META_SYM_ON)77         KEY_MODIFIER.put(KeyEvent.KEYCODE_SYM, KeyEvent.META_SYM_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_FUNCTION, KeyEvent.META_FUNCTION_ON)78         KEY_MODIFIER.put(KeyEvent.KEYCODE_FUNCTION, KeyEvent.META_FUNCTION_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON)79         KEY_MODIFIER.put(KeyEvent.KEYCODE_CTRL_LEFT,
80                 KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_CTRL_RIGHT, KeyEvent.META_CTRL_RIGHT_ON | KeyEvent.META_CTRL_ON)81         KEY_MODIFIER.put(KeyEvent.KEYCODE_CTRL_RIGHT,
82                 KeyEvent.META_CTRL_RIGHT_ON | KeyEvent.META_CTRL_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_META_LEFT, KeyEvent.META_META_LEFT_ON)83         KEY_MODIFIER.put(KeyEvent.KEYCODE_META_LEFT, KeyEvent.META_META_LEFT_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_META_RIGHT, KeyEvent.META_META_RIGHT_ON)84         KEY_MODIFIER.put(KeyEvent.KEYCODE_META_RIGHT, KeyEvent.META_META_RIGHT_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_CAPS_LOCK, KeyEvent.META_CAPS_LOCK_ON)85         KEY_MODIFIER.put(KeyEvent.KEYCODE_CAPS_LOCK, KeyEvent.META_CAPS_LOCK_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_NUM_LOCK, KeyEvent.META_NUM_LOCK_ON)86         KEY_MODIFIER.put(KeyEvent.KEYCODE_NUM_LOCK, KeyEvent.META_NUM_LOCK_ON);
KEY_MODIFIER.put(KeyEvent.KEYCODE_SCROLL_LOCK, KeyEvent.META_SCROLL_LOCK_ON)87         KEY_MODIFIER.put(KeyEvent.KEYCODE_SCROLL_LOCK, KeyEvent.META_SCROLL_LOCK_ON);
88     }
89 
InteractionController(UiDevice device)90     InteractionController(UiDevice device) {
91         mDevice = device;
92     }
93 
94     /**
95      * Predicate for waiting for any of the events specified in the mask
96      */
97     static class WaitForAnyEventPredicate implements AccessibilityEventFilter {
98         final int mMask;
WaitForAnyEventPredicate(int mask)99         WaitForAnyEventPredicate(int mask) {
100             mMask = mask;
101         }
102         @Override
accept(AccessibilityEvent t)103         public boolean accept(AccessibilityEvent t) {
104             // check current event in the list
105             return (t.getEventType() & mMask) != 0;
106         }
107     }
108 
109     /**
110      * Predicate for waiting for every event specified in the mask to be matched at least once
111      */
112     static class WaitForAllEventPredicate implements AccessibilityEventFilter {
113         int mMask;
WaitForAllEventPredicate(int mask)114         WaitForAllEventPredicate(int mask) {
115             mMask = mask;
116         }
117 
118         @Override
accept(AccessibilityEvent t)119         public boolean accept(AccessibilityEvent t) {
120             // check current event in the list
121             if ((t.getEventType() & mMask) != 0) {
122                 // remove from mask since this condition is satisfied
123                 mMask &= ~t.getEventType();
124 
125                 // Since we're waiting for all events to be matched at least once
126                 return mMask == 0;
127             }
128 
129             // no match yet
130             return false;
131         }
132     }
133 
134     /**
135      * Helper used by methods to perform actions and wait for any accessibility events and return
136      * predicated on predefined filter.
137      *
138      * @param command
139      * @param filter
140      * @param timeout
141      * @return
142      */
runAndWaitForEvents(Runnable command, AccessibilityEventFilter filter, long timeout)143     private AccessibilityEvent runAndWaitForEvents(Runnable command,
144             AccessibilityEventFilter filter, long timeout) {
145 
146         try {
147             return getUiAutomation().executeAndWaitForEvent(command, filter, timeout);
148         } catch (TimeoutException e) {
149             Log.w(TAG, String.format("Timed out waiting %dms for command and events.", timeout));
150             return null;
151         } catch (Exception e) {
152             Log.e(TAG, "Exception while waiting for command and events.", e);
153             return null;
154         }
155     }
156 
157     /**
158      * Send keys and blocks until the first specified accessibility event.
159      *
160      * Most key presses will cause some UI change to occur. If the device is busy, this will
161      * block until the device begins to process the key press at which point the call returns
162      * and normal wait for idle processing may begin. If no events are detected for the
163      * timeout period specified, the call will return anyway with false.
164      *
165      * @param keyCode
166      * @param metaState
167      * @param eventType
168      * @param timeout
169      * @return true if events is received, otherwise false.
170      */
sendKeyAndWaitForEvent(final int keyCode, final int metaState, final int eventType, long timeout)171     public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
172             final int eventType, long timeout) {
173         Runnable command = () -> {
174             final long eventTime = SystemClock.uptimeMillis();
175             KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
176                     keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
177                     InputDevice.SOURCE_KEYBOARD);
178             if (injectEventSync(downEvent)) {
179                 KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
180                         keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
181                         InputDevice.SOURCE_KEYBOARD);
182                 injectEventSync(upEvent);
183             }
184         };
185 
186         return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
187                 != null;
188     }
189 
190     /**
191      * Clicks at coordinates without waiting for device idle. This may be used for operations
192      * that require stressing the target.
193      * @param x
194      * @param y
195      * @return true if the click executed successfully
196      */
clickNoSync(int x, int y)197     public boolean clickNoSync(int x, int y) {
198         boolean success = touchDown(x, y);
199         SystemClock.sleep(REGULAR_CLICK_LENGTH);
200         // Always touch up (regardless of touch down success) to ensure the gesture is complete.
201         success &= touchUp(x, y);
202         return success;
203     }
204 
205     /**
206      * Click at coordinates and blocks until either accessibility event TYPE_WINDOW_CONTENT_CHANGED
207      * or TYPE_VIEW_SELECTED are received.
208      *
209      * @param x
210      * @param y
211      * @param timeout waiting for event
212      * @return true if events are received, else false if timeout.
213      */
clickAndSync(final int x, final int y, long timeout)214     public boolean clickAndSync(final int x, final int y, long timeout) {
215         return runAndWaitForEvents(() -> clickNoSync(x, y), new WaitForAnyEventPredicate(
216                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
217                 AccessibilityEvent.TYPE_VIEW_SELECTED), timeout) != null;
218     }
219 
220     /**
221      * Clicks at coordinates and waits for for a TYPE_WINDOW_STATE_CHANGED event followed
222      * by TYPE_WINDOW_CONTENT_CHANGED. If timeout occurs waiting for TYPE_WINDOW_STATE_CHANGED,
223      * no further waits will be performed and the function returns.
224      * @param x
225      * @param y
226      * @param timeout waiting for event
227      * @return true if both events occurred in the expected order
228      */
clickAndWaitForNewWindow(final int x, final int y, long timeout)229     public boolean clickAndWaitForNewWindow(final int x, final int y, long timeout) {
230         return runAndWaitForEvents(() -> clickNoSync(x, y), new WaitForAllEventPredicate(
231                 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
232                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), timeout) != null;
233     }
234 
235     /**
236      * Touches down for a long press at the specified coordinates.
237      *
238      * @param x
239      * @param y
240      * @return true if successful.
241      */
longTapNoSync(int x, int y)242     public boolean longTapNoSync(int x, int y) {
243         boolean success = touchDown(x, y);
244         SystemClock.sleep(LONG_PRESS_DURATION_MS);
245         // Always touch up (regardless of touch down success) to ensure the gesture is complete.
246         success &= touchUp(x, y);
247         return success;
248     }
249 
250     /**
251      * Long tap at coordinates and blocks until either accessibility event
252      * TYPE_WINDOW_CONTENT_CHANGED or TYPE_VIEW_SELECTED are received.
253      *
254      * @param x
255      * @param y
256      * @param timeout waiting for event
257      * @return true if events are received, else false if timeout.
258      */
longTapAndSync(final int x, final int y, long timeout)259     public boolean longTapAndSync(final int x, final int y, long timeout) {
260         return runAndWaitForEvents(() -> longTapNoSync(x, y), new WaitForAnyEventPredicate(
261                 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
262                 AccessibilityEvent.TYPE_VIEW_SELECTED), timeout) != null;
263     }
264 
touchDown(int x, int y)265     boolean touchDown(int x, int y) {
266         mDownTime = SystemClock.uptimeMillis();
267         MotionEvent event = getMotionEvent(mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y);
268         return injectEventSync(event);
269     }
270 
touchUp(int x, int y)271     boolean touchUp(int x, int y) {
272         final long eventTime = SystemClock.uptimeMillis();
273         MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_UP, x, y);
274         mDownTime = 0;
275         return injectEventSync(event);
276     }
277 
touchMove(int x, int y)278     private boolean touchMove(int x, int y) {
279         final long eventTime = SystemClock.uptimeMillis();
280         MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_MOVE, x, y);
281         return injectEventSync(event);
282     }
283 
284     /**
285      * Handle swipes in any direction where the result is a scroll event. This call blocks
286      * until the UI has fired a scroll event or timeout.
287      * @param downX
288      * @param downY
289      * @param upX
290      * @param upY
291      * @param steps
292      * @return true if we are not at the beginning or end of the scrollable view.
293      */
scrollSwipe(final int downX, final int downY, final int upX, final int upY, final int steps)294     public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
295             final int steps) {
296         Runnable command = () -> swipe(downX, downY, upX, upY, steps);
297 
298         // Get scroll direction based on position.
299         Direction direction;
300         if (Math.abs(downX - upX) > Math.abs(downY - upY)) {
301             // Horizontal.
302             direction = downX > upX ? Direction.RIGHT : Direction.LEFT;
303         } else {
304             // Vertical.
305             direction = downY > upY ? Direction.DOWN : Direction.UP;
306         }
307         EventCondition<Boolean> condition = Until.scrollFinished(direction);
308         runAndWaitForEvents(command,
309                 condition,
310                 Configurator.getInstance().getScrollAcknowledgmentTimeout());
311 
312         return Boolean.FALSE.equals(condition.getResult());
313     }
314 
315     /**
316      * Handle swipes in any direction.
317      * @param downX
318      * @param downY
319      * @param upX
320      * @param upY
321      * @param steps
322      * @return true if the swipe executed successfully
323      */
swipe(int downX, int downY, int upX, int upY, int steps)324     public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
325         return swipe(downX, downY, upX, upY, steps, false /*drag*/);
326     }
327 
328     /**
329      * Handle swipes/drags in any direction.
330      * @param downX
331      * @param downY
332      * @param upX
333      * @param upY
334      * @param steps
335      * @param drag when true, the swipe becomes a drag swipe
336      * @return true if the swipe executed successfully
337      */
swipe(int downX, int downY, int upX, int upY, int steps, boolean drag)338     public boolean swipe(int downX, int downY, int upX, int upY, int steps, boolean drag) {
339         boolean ret;
340         int swipeSteps = steps;
341         double xStep, yStep;
342 
343         // avoid a divide by zero
344         if(swipeSteps == 0)
345             swipeSteps = 1;
346 
347         xStep = ((double)(upX - downX)) / swipeSteps;
348         yStep = ((double)(upY - downY)) / swipeSteps;
349 
350         // first touch starts exactly at the point requested
351         ret = touchDown(downX, downY);
352         SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
353         if (drag)
354             SystemClock.sleep(LONG_PRESS_DURATION_MS);
355         for(int i = 1; i < swipeSteps; i++) {
356             ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
357             if (!ret) {
358                 break;
359             }
360             // set some known constant delay between steps as without it this
361             // become completely dependent on the speed of the system and results
362             // may vary on different devices. This guarantees at minimum we have
363             // a preset delay.
364             SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
365         }
366         if (drag)
367             SystemClock.sleep(REGULAR_CLICK_LENGTH);
368         ret &= touchUp(upX, upY);
369         return ret;
370     }
371 
372     /**
373      * Performs a swipe between points in the Point array.
374      * @param segments is Point array containing at least one Point object
375      * @param segmentSteps steps to inject between two Points
376      * @return true on success
377      */
swipe(Point[] segments, int segmentSteps)378     public boolean swipe(Point[] segments, int segmentSteps) {
379         boolean ret;
380         int swipeSteps = segmentSteps;
381         double xStep, yStep;
382 
383         // avoid a divide by zero
384         if(segmentSteps == 0)
385             segmentSteps = 1;
386 
387         // must have some points
388         if(segments.length == 0)
389             return false;
390 
391         // first touch starts exactly at the point requested
392         ret = touchDown(segments[0].x, segments[0].y);
393         SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
394         for(int seg = 0; seg < segments.length; seg++) {
395             if(seg + 1 < segments.length) {
396 
397                 xStep = ((double)(segments[seg+1].x - segments[seg].x)) / segmentSteps;
398                 yStep = ((double)(segments[seg+1].y - segments[seg].y)) / segmentSteps;
399 
400                 for(int i = 1; i < swipeSteps; i++) {
401                     ret &= touchMove(segments[seg].x + (int)(xStep * i),
402                             segments[seg].y + (int)(yStep * i));
403                     if (!ret) {
404                         break;
405                     }
406                     // set some known constant delay between steps as without it this
407                     // become completely dependent on the speed of the system and results
408                     // may vary on different devices. This guarantees at minimum we have
409                     // a preset delay.
410                     SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
411                 }
412             }
413         }
414         ret &= touchUp(segments[segments.length - 1].x, segments[segments.length -1].y);
415         return ret;
416     }
417 
sendKey(int keyCode, int metaState)418     public boolean sendKey(int keyCode, int metaState) {
419         return sendKeys(new int[]{keyCode}, metaState);
420     }
421 
422     /**
423      * Send multiple keys
424      *
425      * @param keyCodes array of keycode
426      * @param metaState the pressed state of key modifiers
427      * @return true if keys are sent.
428      */
sendKeys(int[] keyCodes, int metaState)429     public boolean sendKeys(int[] keyCodes, int metaState) {
430         final long eventTime = SystemClock.uptimeMillis();
431         for (int keyCode : keyCodes) {
432             if (KEY_MODIFIER.containsKey(keyCode)) {
433                 metaState |= KEY_MODIFIER.get(keyCode);
434             }
435             KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
436                     keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
437                     InputDevice.SOURCE_KEYBOARD);
438             if (!injectEventSync(downEvent)) {
439                 return false;
440             }
441         }
442         for (int keyCode : keyCodes) {
443             KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
444                     keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
445                     InputDevice.SOURCE_KEYBOARD);
446             if (!injectEventSync(upEvent)) {
447                 return false;
448             }
449             if (KEY_MODIFIER.containsKey(keyCode)) {
450                 metaState &= ~KEY_MODIFIER.get(keyCode);
451             }
452         }
453         return true;
454     }
455 
456     /**
457      * This method simply presses the power button if the screen is OFF else
458      * it does nothing if the screen is already ON.
459      * On API 20 or later devices, this will press the wakeup button instead.
460      * @return true if the device was asleep else false
461      * @throws RemoteException
462      */
wakeDevice()463     public boolean wakeDevice() throws RemoteException {
464         if(!isScreenOn()) {
465             sendKey(KeyEvent.KEYCODE_WAKEUP, 0);
466             return true;
467         }
468         return false;
469     }
470 
471     /**
472      * This method simply presses the power button if the screen is ON else
473      * it does nothing if the screen is already OFF.
474      * On API 20 or later devices, this will press the sleep button instead.
475      * @return true if the device was awake else false
476      * @throws RemoteException
477      */
sleepDevice()478     public boolean sleepDevice() throws RemoteException {
479         if(isScreenOn()) {
480             sendKey(KeyEvent.KEYCODE_SLEEP , 0);
481             return true;
482         }
483         return false;
484     }
485 
486     /**
487      * Checks the power manager if the screen is ON
488      * @return true if the screen is ON else false
489      */
isScreenOn()490     public boolean isScreenOn() {
491         PowerManager pm = (PowerManager) mDevice.getInstrumentation().getContext().getSystemService(
492                 Service.POWER_SERVICE);
493         return pm.isScreenOn();
494     }
495 
injectEventSync(InputEvent event)496     boolean injectEventSync(InputEvent event) {
497         return getUiAutomation().injectInputEvent(event, true);
498     }
499 
getPointerAction(int motionEnvent, int index)500     private int getPointerAction(int motionEnvent, int index) {
501         return motionEnvent + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
502     }
503 
504     /**
505      * Performs a multi-touch gesture
506      *
507      * Takes a series of touch coordinates for at least 2 pointers. Each pointer must have
508      * all of its touch steps defined in an array of {@link PointerCoords}. By having the ability
509      * to specify the touch points along the path of a pointer, the caller is able to specify
510      * complex gestures like circles, irregular shapes etc, where each pointer may take a
511      * different path.
512      *
513      * To create a single point on a pointer's touch path
514      * <code>
515      *       PointerCoords p = new PointerCoords();
516      *       p.x = stepX;
517      *       p.y = stepY;
518      *       p.pressure = 1;
519      *       p.size = 1;
520      * </code>
521      * @param touches each array of {@link PointerCoords} constitute a single pointer's touch path.
522      *        Multiple {@link PointerCoords} arrays constitute multiple pointers, each with its own
523      *        path. Each {@link PointerCoords} in an array constitute a point on a pointer's path.
524      * @return <code>true</code> if all points on all paths are injected successfully, <code>false
525      *        </code>otherwise
526      */
performMultiPointerGesture(PointerCoords[] .... touches)527     public boolean performMultiPointerGesture(PointerCoords[] ... touches) {
528         boolean ret;
529         if (touches.length < 2) {
530             throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
531         }
532 
533         // Get the pointer with the max steps to inject.
534         int maxSteps = 0;
535         for (PointerCoords[] touch : touches) maxSteps = Math.max(maxSteps, touch.length);
536 
537         // specify the properties for each pointer as finger touch
538         PointerProperties[] properties = new PointerProperties[touches.length];
539         PointerCoords[] pointerCoords = new PointerCoords[touches.length];
540         for (int x = 0; x < touches.length; x++) {
541             PointerProperties prop = new PointerProperties();
542             prop.id = x;
543             prop.toolType = Configurator.getInstance().getToolType();
544             properties[x] = prop;
545 
546             // for each pointer set the first coordinates for touch down
547             pointerCoords[x] = touches[x][0];
548         }
549 
550         // Touch down all pointers
551         long downTime = SystemClock.uptimeMillis();
552         MotionEvent event;
553         event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1,
554                 properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
555         ret = injectEventSync(event);
556 
557         for (int x = 1; x < touches.length; x++) {
558             event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
559                     getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties,
560                     pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
561             ret &= injectEventSync(event);
562         }
563         SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
564 
565         // Move all pointers
566         for (int i = 1; i < maxSteps - 1; i++) {
567             // for each pointer
568             for (int x = 0; x < touches.length; x++) {
569                 // check if it has coordinates to move
570                 if (touches[x].length > i)
571                     pointerCoords[x] = touches[x][i];
572                 else
573                     pointerCoords[x] = touches[x][touches[x].length - 1];
574             }
575 
576             event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
577                     MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1,
578                     0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
579 
580             ret &= injectEventSync(event);
581             SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
582         }
583 
584         // For each pointer get the last coordinates
585         for (int x = 0; x < touches.length; x++)
586             pointerCoords[x] = touches[x][touches[x].length - 1];
587 
588         // touch up
589         for (int x = 1; x < touches.length; x++) {
590             event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
591                     getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties,
592                     pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
593             ret &= injectEventSync(event);
594         }
595 
596         // first to touch down is last up
597         event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1,
598                 properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
599         ret &= injectEventSync(event);
600         return ret;
601     }
602 
603     /** Helper function to obtain a MotionEvent. */
getMotionEvent(long downTime, long eventTime, int action, float x, float y)604     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
605             float x, float y) {
606 
607         PointerProperties properties = new PointerProperties();
608         properties.id = 0;
609         properties.toolType = Configurator.getInstance().getToolType();
610 
611         PointerCoords coords = new PointerCoords();
612         coords.pressure = 1;
613         coords.size = 1;
614         coords.x = x;
615         coords.y = y;
616 
617         return MotionEvent.obtain(downTime, eventTime, action, 1,
618                 new PointerProperties[] { properties }, new PointerCoords[] { coords },
619                 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
620     }
621 
getUiAutomation()622     UiAutomation getUiAutomation() {
623         return mDevice.getUiAutomation();
624     }
625 }
626