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 17package com.android.uiautomator.core; 18 19import android.app.ActivityManagerNative; 20import android.app.IActivityManager; 21import android.app.IActivityManager.ContentProviderHolder; 22import android.content.Context; 23import android.content.IContentProvider; 24import android.database.Cursor; 25import android.graphics.Point; 26import android.hardware.input.InputManager; 27import android.os.Binder; 28import android.os.IBinder; 29import android.os.IPowerManager; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.os.SystemClock; 33import android.os.UserHandle; 34import android.provider.Settings; 35import android.util.Log; 36import android.view.IWindowManager; 37import android.view.InputDevice; 38import android.view.InputEvent; 39import android.view.KeyCharacterMap; 40import android.view.KeyEvent; 41import android.view.MotionEvent; 42import android.view.Surface; 43import android.view.accessibility.AccessibilityEvent; 44 45import com.android.internal.util.Predicate; 46 47import java.util.concurrent.TimeoutException; 48 49/** 50 * The InteractionProvider is responsible for injecting user events such as touch events 51 * (includes swipes) and text key events into the system. To do so, all it needs to know about 52 * are coordinates of the touch events and text for the text input events. 53 * The InteractionController performs no synchronization. It will fire touch and text input events 54 * as fast as it receives them. All idle synchronization is performed prior to querying the 55 * hierarchy. See {@link QueryController} 56 */ 57class InteractionController { 58 59 private static final String LOG_TAG = InteractionController.class.getSimpleName(); 60 61 private static final boolean DEBUG = false; 62 63 private static final long DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS = 500; 64 65 private final KeyCharacterMap mKeyCharacterMap = 66 KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 67 68 private final UiAutomatorBridge mUiAutomatorBridge; 69 70 private final IWindowManager mWindowManager; 71 72 private final long mLongPressTimeout; 73 74 private static final long REGULAR_CLICK_LENGTH = 100; 75 76 private long mDownTime; 77 78 public InteractionController(UiAutomatorBridge bridge) { 79 mUiAutomatorBridge = bridge; 80 81 // Obtain the window manager. 82 mWindowManager = IWindowManager.Stub.asInterface( 83 ServiceManager.getService(Context.WINDOW_SERVICE)); 84 if (mWindowManager == null) { 85 throw new RuntimeException("Unable to connect to WindowManager, " 86 + "is the system running?"); 87 } 88 89 // the value returned is on the border of going undetected as used 90 // by this framework during long presses. Adding few extra 100ms 91 // of long press time helps ensure long enough time for a valid 92 // longClick detection. 93 mLongPressTimeout = getSystemLongPressTime() * 2 + 100; 94 } 95 96 /** 97 * Get the system long press time 98 * @return milliseconds 99 */ 100 private long getSystemLongPressTime() { 101 // Read the long press timeout setting. 102 long longPressTimeout = 0; 103 try { 104 IContentProvider provider = null; 105 Cursor cursor = null; 106 IActivityManager activityManager = ActivityManagerNative.getDefault(); 107 String providerName = Settings.Secure.CONTENT_URI.getAuthority(); 108 IBinder token = new Binder(); 109 try { 110 ContentProviderHolder holder = activityManager.getContentProviderExternal( 111 providerName, UserHandle.USER_OWNER, token); 112 if (holder == null) { 113 throw new IllegalStateException("Could not find provider: " + providerName); 114 } 115 provider = holder.provider; 116 cursor = provider.query(Settings.Secure.CONTENT_URI, 117 new String[] {Settings.Secure.VALUE}, "name=?", 118 new String[] {Settings.Secure.LONG_PRESS_TIMEOUT}, null, null); 119 if (cursor.moveToFirst()) { 120 longPressTimeout = cursor.getInt(0); 121 } 122 } finally { 123 if (cursor != null) { 124 cursor.close(); 125 } 126 if (provider != null) { 127 activityManager.removeContentProviderExternal(providerName, token); 128 } 129 } 130 } catch (RemoteException e) { 131 String message = "Error reading long press timeout setting."; 132 Log.e(LOG_TAG, message, e); 133 throw new RuntimeException(message, e); 134 } 135 return longPressTimeout; 136 } 137 138 /** 139 * Click at coordinates and blocks until the first specified accessibility event. 140 * 141 * All clicks will cause some UI change to occur. If the device is busy, this will 142 * block until the device begins to process the click at which point the call returns 143 * and normal wait for idle processing may begin. If no evens are detected for the 144 * timeout period specified, the call will return anyway. 145 * @param x 146 * @param y 147 * @param timeout 148 * @param eventType is an {@link AccessibilityEvent} type 149 * @return True if busy state is detected else false for timeout waiting for busy state 150 */ 151 public boolean clickAndWaitForEvent(final int x, final int y, long timeout, 152 final int eventType) { 153 return clickAndWaitForEvents(x, y, timeout, false, eventType); 154 } 155 156 /** 157 * Click at coordinates and blocks until the specified accessibility events. It is possible to 158 * set the wait for all events to occur, in no specific order, or to the wait for any. 159 * 160 * @param x 161 * @param y 162 * @param timeout 163 * @param waitForAll boolean to indicate whether to wait for any or all events 164 * @param eventTypes mask 165 * @return 166 */ 167 public boolean clickAndWaitForEvents(final int x, final int y, long timeout, 168 boolean waitForAll, int eventTypes) { 169 String logString = String.format("clickAndWaitForEvents(%d, %d, %d, %s, %d)", x, y, timeout, 170 Boolean.toString(waitForAll), eventTypes); 171 Log.d(LOG_TAG, logString); 172 173 mUiAutomatorBridge.setOperationTime(); 174 Runnable command = new Runnable() { 175 @Override 176 public void run() { 177 if(touchDown(x, y)) { 178 SystemClock.sleep(REGULAR_CLICK_LENGTH); 179 touchUp(x, y); 180 } 181 } 182 }; 183 return runAndWaitForEvents(command, timeout, waitForAll, eventTypes); 184 } 185 186 /** 187 * Runs a command and waits for a specific accessibility event. 188 * @param command is a Runnable to execute before waiting for the event. 189 * @param timeout 190 * @param eventType 191 * @return 192 */ 193 private boolean runAndWaitForEvent(Runnable command, long timeout, int eventType) { 194 return runAndWaitForEvents(command, timeout, false, eventType); 195 } 196 197 /** 198 * Runs a command and waits for accessibility events. It is possible to set the wait for all 199 * events to occur at least once for each, or wait for any one to occur at least once. 200 * 201 * @param command 202 * @param timeout 203 * @param waitForAll boolean to indicate whether to wait for any or all events 204 * @param eventTypesMask 205 * @return 206 */ 207 private boolean runAndWaitForEvents(Runnable command, long timeout, final boolean waitForAll, 208 final int eventTypesMask) { 209 210 if (eventTypesMask == 0) 211 throw new IllegalArgumentException("events mask cannot be zero"); 212 213 class EventPredicate implements Predicate<AccessibilityEvent> { 214 int mMask; 215 EventPredicate(int mask) { 216 mMask = mask; 217 } 218 @Override 219 public boolean apply(AccessibilityEvent t) { 220 // check current event in the list 221 if ((t.getEventType() & mMask) != 0) { 222 if (!waitForAll) 223 return true; 224 225 // remove from mask since this condition is satisfied 226 mMask &= ~t.getEventType(); 227 228 // Since we're waiting for all events to be matched at least once 229 if (mMask != 0) 230 return false; 231 232 // all matched 233 return true; 234 } 235 // not one of our events 236 return false; 237 } 238 } 239 240 try { 241 mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command, 242 new EventPredicate(eventTypesMask), timeout); 243 } catch (TimeoutException e) { 244 Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events: " + eventTypesMask); 245 return false; 246 } catch (Exception e) { 247 Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e); 248 return false; 249 } 250 return true; 251 } 252 253 /** 254 * Send keys and blocks until the first specified accessibility event. 255 * 256 * Most key presses will cause some UI change to occur. If the device is busy, this will 257 * block until the device begins to process the key press at which point the call returns 258 * and normal wait for idle processing may begin. If no evens are detected for the 259 * timeout period specified, the call will return anyway with false. 260 * 261 * @param keyCode 262 * @param metaState 263 * @param eventType 264 * @param timeout 265 * @return 266 */ 267 public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState, 268 final int eventType, long timeout) { 269 mUiAutomatorBridge.setOperationTime(); 270 Runnable command = new Runnable() { 271 @Override 272 public void run() { 273 final long eventTime = SystemClock.uptimeMillis(); 274 KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN, 275 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, 276 InputDevice.SOURCE_KEYBOARD, null); 277 if (injectEventSync(downEvent)) { 278 KeyEvent upEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_UP, 279 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, 280 InputDevice.SOURCE_KEYBOARD, null); 281 injectEventSync(upEvent); 282 } 283 } 284 }; 285 286 return runAndWaitForEvent(command, timeout, eventType); 287 } 288 289 /** 290 * Clicks at coordinates without waiting for device idle. This may be used for operations 291 * that require stressing the target. 292 * @param x 293 * @param y 294 * @return 295 */ 296 public boolean click(int x, int y) { 297 Log.d(LOG_TAG, "click (" + x + ", " + y + ")"); 298 mUiAutomatorBridge.setOperationTime(); 299 300 if (touchDown(x, y)) { 301 SystemClock.sleep(REGULAR_CLICK_LENGTH); 302 if (touchUp(x, y)) 303 return true; 304 } 305 return false; 306 } 307 308 /** 309 * Clicks at coordinates and waits for for a TYPE_WINDOW_STATE_CHANGED event followed 310 * by TYPE_WINDOW_CONTENT_CHANGED. If timeout occurs waiting for TYPE_WINDOW_STATE_CHANGED, 311 * no further waits will be performed and the function returns. 312 * @param x 313 * @param y 314 * @param timeout 315 * @return true if both events occurred in the expected order 316 */ 317 public boolean clickAndWaitForNewWindow(final int x, final int y, long timeout) { 318 return (clickAndWaitForEvents(x, y, timeout, true, 319 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + 320 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)); 321 } 322 323 public boolean longTap(int x, int y) { 324 if (DEBUG) { 325 Log.d(LOG_TAG, "longTap (" + x + ", " + y + ")"); 326 } 327 328 mUiAutomatorBridge.setOperationTime(); 329 if (touchDown(x, y)) { 330 SystemClock.sleep(mLongPressTimeout); 331 if(touchUp(x, y)) { 332 return true; 333 } 334 } 335 return false; 336 } 337 338 private boolean touchDown(int x, int y) { 339 if (DEBUG) { 340 Log.d(LOG_TAG, "touchDown (" + x + ", " + y + ")"); 341 } 342 mDownTime = SystemClock.uptimeMillis(); 343 MotionEvent event = MotionEvent.obtain( 344 mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y, 1); 345 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 346 return injectEventSync(event); 347 } 348 349 private boolean touchUp(int x, int y) { 350 if (DEBUG) { 351 Log.d(LOG_TAG, "touchUp (" + x + ", " + y + ")"); 352 } 353 final long eventTime = SystemClock.uptimeMillis(); 354 MotionEvent event = MotionEvent.obtain( 355 mDownTime, eventTime, MotionEvent.ACTION_UP, x, y, 1); 356 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 357 mDownTime = 0; 358 return injectEventSync(event); 359 } 360 361 private boolean touchMove(int x, int y) { 362 if (DEBUG) { 363 Log.d(LOG_TAG, "touchMove (" + x + ", " + y + ")"); 364 } 365 final long eventTime = SystemClock.uptimeMillis(); 366 MotionEvent event = MotionEvent.obtain( 367 mDownTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 1); 368 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 369 return injectEventSync(event); 370 } 371 372 /** 373 * Handle swipes in any direction where the result is a scroll event. This call blocks 374 * until the UI has fired a scroll event or timeout. 375 * @param downX 376 * @param downY 377 * @param upX 378 * @param upY 379 * @param duration 380 * @return true if the swipe and scrolling have been successfully completed. 381 */ 382 public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY, 383 final int steps) { 384 Log.d(LOG_TAG, "scrollSwipe (" + downX + ", " + downY + ", " + upX + ", " 385 + upY + ", " + steps +")"); 386 387 Runnable command = new Runnable() { 388 @Override 389 public void run() { 390 swipe(downX, downY, upX, upY, steps); 391 } 392 }; 393 394 return runAndWaitForEvent(command, DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS, 395 AccessibilityEvent.TYPE_VIEW_SCROLLED); 396 } 397 398 /** 399 * Handle swipes in any direction. 400 * @param downX 401 * @param downY 402 * @param upX 403 * @param upY 404 * @param duration 405 * @return 406 */ 407 public boolean swipe(int downX, int downY, int upX, int upY, int steps) { 408 boolean ret = false; 409 int swipeSteps = steps; 410 double xStep = 0; 411 double yStep = 0; 412 413 // avoid a divide by zero 414 if(swipeSteps == 0) 415 swipeSteps = 1; 416 417 xStep = ((double)(upX - downX)) / swipeSteps; 418 yStep = ((double)(upY - downY)) / swipeSteps; 419 420 // first touch starts exactly at the point requested 421 ret = touchDown(downX, downY); 422 for(int i = 1; i < swipeSteps; i++) { 423 ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i)); 424 if(ret == false) 425 break; 426 // set some known constant delay between steps as without it this 427 // become completely dependent on the speed of the system and results 428 // may vary on different devices. This guarantees at minimum we have 429 // a preset delay. 430 SystemClock.sleep(5); 431 } 432 ret &= touchUp(upX, upY); 433 return(ret); 434 } 435 436 /** 437 * Performs a swipe between points in the Point array. 438 * @param segments is Point array containing at least one Point object 439 * @param segmentSteps steps to inject between two Points 440 * @return true on success 441 */ 442 public boolean swipe(Point[] segments, int segmentSteps) { 443 boolean ret = false; 444 int swipeSteps = segmentSteps; 445 double xStep = 0; 446 double yStep = 0; 447 448 // avoid a divide by zero 449 if(segmentSteps == 0) 450 segmentSteps = 1; 451 452 // must have some points 453 if(segments.length == 0) 454 return false; 455 456 // first touch starts exactly at the point requested 457 ret = touchDown(segments[0].x, segments[0].y); 458 for(int seg = 0; seg < segments.length; seg++) { 459 if(seg + 1 < segments.length) { 460 461 xStep = ((double)(segments[seg+1].x - segments[seg].x)) / segmentSteps; 462 yStep = ((double)(segments[seg+1].y - segments[seg].y)) / segmentSteps; 463 464 for(int i = 1; i < swipeSteps; i++) { 465 ret &= touchMove(segments[seg].x + (int)(xStep * i), 466 segments[seg].y + (int)(yStep * i)); 467 if(ret == false) 468 break; 469 // set some known constant delay between steps as without it this 470 // become completely dependent on the speed of the system and results 471 // may vary on different devices. This guarantees at minimum we have 472 // a preset delay. 473 SystemClock.sleep(5); 474 } 475 } 476 } 477 ret &= touchUp(segments[segments.length - 1].x, segments[segments.length -1].y); 478 return(ret); 479 } 480 481 482 public boolean sendText(String text) { 483 if (DEBUG) { 484 Log.d(LOG_TAG, "sendText (" + text + ")"); 485 } 486 487 mUiAutomatorBridge.setOperationTime(); 488 KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray()); 489 if (events != null) { 490 for (KeyEvent event2 : events) { 491 // We have to change the time of an event before injecting it because 492 // all KeyEvents returned by KeyCharacterMap.getEvents() have the same 493 // time stamp and the system rejects too old events. Hence, it is 494 // possible for an event to become stale before it is injected if it 495 // takes too long to inject the preceding ones. 496 KeyEvent event = KeyEvent.changeTimeRepeat(event2, 497 SystemClock.uptimeMillis(), 0); 498 if (!injectEventSync(event)) { 499 return false; 500 } 501 } 502 } 503 return true; 504 } 505 506 public boolean sendKey(int keyCode, int metaState) { 507 if (DEBUG) { 508 Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")"); 509 } 510 511 mUiAutomatorBridge.setOperationTime(); 512 final long eventTime = SystemClock.uptimeMillis(); 513 KeyEvent downEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_DOWN, 514 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, 515 InputDevice.SOURCE_KEYBOARD, null); 516 if (injectEventSync(downEvent)) { 517 KeyEvent upEvent = KeyEvent.obtain(eventTime, eventTime, KeyEvent.ACTION_UP, 518 keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, 519 InputDevice.SOURCE_KEYBOARD, null); 520 if(injectEventSync(upEvent)) { 521 return true; 522 } 523 } 524 return false; 525 } 526 527 /** 528 * Check if the device is in its natural orientation. This is determined by 529 * checking whether the orientation is at 0 or 180 degrees. 530 * @return true if it is in natural orientation 531 * @throws RemoteException 532 */ 533 public boolean isNaturalRotation() throws RemoteException { 534 return mWindowManager.getRotation() == Surface.ROTATION_0 535 || mWindowManager.getRotation() == Surface.ROTATION_180; 536 } 537 538 /** 539 * Rotates right and also freezes rotation in that position by 540 * disabling the sensors. If you want to un-freeze the rotation 541 * and re-enable the sensors see {@link #unfreezeRotation()}. Note 542 * that doing so may cause the screen contents to rotate 543 * depending on the current physical position of the test device. 544 * @throws RemoteException 545 */ 546 public void setRotationRight() throws RemoteException { 547 mWindowManager.freezeRotation(Surface.ROTATION_270); 548 } 549 550 /** 551 * Rotates left and also freezes rotation in that position by 552 * disabling the sensors. If you want to un-freeze the rotation 553 * and re-enable the sensors see {@link #unfreezeRotation()}. Note 554 * that doing so may cause the screen contents to rotate 555 * depending on the current physical position of the test device. 556 * @throws RemoteException 557 */ 558 public void setRotationLeft() throws RemoteException { 559 mWindowManager.freezeRotation(Surface.ROTATION_90); 560 } 561 562 /** 563 * Rotates up and also freezes rotation in that position by 564 * disabling the sensors. If you want to un-freeze the rotation 565 * and re-enable the sensors see {@link #unfreezeRotation()}. Note 566 * that doing so may cause the screen contents to rotate 567 * depending on the current physical position of the test device. 568 * @throws RemoteException 569 */ 570 public void setRotationNatural() throws RemoteException { 571 mWindowManager.freezeRotation(Surface.ROTATION_0); 572 } 573 574 /** 575 * Disables the sensors and freezes the device rotation at its 576 * current rotation state. 577 * @throws RemoteException 578 */ 579 public void freezeRotation() throws RemoteException { 580 mWindowManager.freezeRotation(-1); 581 } 582 583 /** 584 * Re-enables the sensors and un-freezes the device rotation 585 * allowing its contents to rotate with the device physical rotation. 586 * @throws RemoteException 587 */ 588 public void unfreezeRotation() throws RemoteException { 589 mWindowManager.thawRotation(); 590 } 591 592 /** 593 * This method simply presses the power button if the screen is OFF else 594 * it does nothing if the screen is already ON. 595 * @return true if the device was asleep else false 596 * @throws RemoteException 597 */ 598 public boolean wakeDevice() throws RemoteException { 599 if(!isScreenOn()) { 600 sendKey(KeyEvent.KEYCODE_POWER, 0); 601 return true; 602 } 603 return false; 604 } 605 606 /** 607 * This method simply presses the power button if the screen is ON else 608 * it does nothing if the screen is already OFF. 609 * @return true if the device was awake else false 610 * @throws RemoteException 611 */ 612 public boolean sleepDevice() throws RemoteException { 613 if(isScreenOn()) { 614 this.sendKey(KeyEvent.KEYCODE_POWER, 0); 615 return true; 616 } 617 return false; 618 } 619 620 /** 621 * Checks the power manager if the screen is ON 622 * @return true if the screen is ON else false 623 * @throws RemoteException 624 */ 625 public boolean isScreenOn() throws RemoteException { 626 IPowerManager pm = 627 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE)); 628 return pm.isScreenOn(); 629 } 630 631 private static boolean injectEventSync(InputEvent event) { 632 return InputManager.getInstance().injectInputEvent(event, 633 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); 634 } 635} 636