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