1 /* 2 * Copyright (C) 2013 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 android.app; 18 19 import android.accessibilityservice.AccessibilityService.Callbacks; 20 import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper; 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.accessibilityservice.IAccessibilityServiceClient; 23 import android.accessibilityservice.IAccessibilityServiceConnection; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Point; 27 import android.hardware.display.DisplayManagerGlobal; 28 import android.os.Looper; 29 import android.os.ParcelFileDescriptor; 30 import android.os.RemoteException; 31 import android.os.SystemClock; 32 import android.util.Log; 33 import android.view.Display; 34 import android.view.InputEvent; 35 import android.view.KeyEvent; 36 import android.view.Surface; 37 import android.view.WindowAnimationFrameStats; 38 import android.view.WindowContentFrameStats; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.view.accessibility.AccessibilityInteractionClient; 41 import android.view.accessibility.AccessibilityNodeInfo; 42 import android.view.accessibility.AccessibilityWindowInfo; 43 import android.view.accessibility.IAccessibilityInteractionConnection; 44 import libcore.io.IoUtils; 45 46 import java.io.IOException; 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.concurrent.TimeoutException; 50 51 /** 52 * Class for interacting with the device's UI by simulation user actions and 53 * introspection of the screen content. It relies on the platform accessibility 54 * APIs to introspect the screen and to perform some actions on the remote view 55 * tree. It also allows injecting of arbitrary raw input events simulating user 56 * interaction with keyboards and touch devices. One can think of a UiAutomation 57 * as a special type of {@link android.accessibilityservice.AccessibilityService} 58 * which does not provide hooks for the service life cycle and exposes other 59 * APIs that are useful for UI test automation. 60 * <p> 61 * The APIs exposed by this class are low-level to maximize flexibility when 62 * developing UI test automation tools and libraries. Generally, a UiAutomation 63 * client should be using a higher-level library or implement high-level functions. 64 * For example, performing a tap on the screen requires construction and injecting 65 * of a touch down and up events which have to be delivered to the system by a 66 * call to {@link #injectInputEvent(InputEvent, boolean)}. 67 * </p> 68 * <p> 69 * The APIs exposed by this class operate across applications enabling a client 70 * to write tests that cover use cases spanning over multiple applications. For 71 * example, going to the settings application to change a setting and then 72 * interacting with another application whose behavior depends on that setting. 73 * </p> 74 */ 75 public final class UiAutomation { 76 77 private static final String LOG_TAG = UiAutomation.class.getSimpleName(); 78 79 private static final boolean DEBUG = false; 80 81 private static final int CONNECTION_ID_UNDEFINED = -1; 82 83 private static final long CONNECT_TIMEOUT_MILLIS = 5000; 84 85 /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */ 86 public static final int ROTATION_UNFREEZE = -2; 87 88 /** Rotation constant: Freeze rotation to its current state. */ 89 public static final int ROTATION_FREEZE_CURRENT = -1; 90 91 /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */ 92 public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0; 93 94 /** Rotation constant: Freeze rotation to 90 degrees . */ 95 public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90; 96 97 /** Rotation constant: Freeze rotation to 180 degrees . */ 98 public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180; 99 100 /** Rotation constant: Freeze rotation to 270 degrees . */ 101 public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270; 102 103 private final Object mLock = new Object(); 104 105 private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>(); 106 107 private final IAccessibilityServiceClient mClient; 108 109 private final IUiAutomationConnection mUiAutomationConnection; 110 111 private int mConnectionId = CONNECTION_ID_UNDEFINED; 112 113 private OnAccessibilityEventListener mOnAccessibilityEventListener; 114 115 private boolean mWaitingForEventDelivery; 116 117 private long mLastEventTimeMillis; 118 119 private boolean mIsConnecting; 120 121 /** 122 * Listener for observing the {@link AccessibilityEvent} stream. 123 */ 124 public static interface OnAccessibilityEventListener { 125 126 /** 127 * Callback for receiving an {@link AccessibilityEvent}. 128 * <p> 129 * <strong>Note:</strong> This method is <strong>NOT</strong> executed 130 * on the main test thread. The client is responsible for proper 131 * synchronization. 132 * </p> 133 * <p> 134 * <strong>Note:</strong> It is responsibility of the client 135 * to recycle the received events to minimize object creation. 136 * </p> 137 * 138 * @param event The received event. 139 */ 140 public void onAccessibilityEvent(AccessibilityEvent event); 141 } 142 143 /** 144 * Listener for filtering accessibility events. 145 */ 146 public static interface AccessibilityEventFilter { 147 148 /** 149 * Callback for determining whether an event is accepted or 150 * it is filtered out. 151 * 152 * @param event The event to process. 153 * @return True if the event is accepted, false to filter it out. 154 */ 155 public boolean accept(AccessibilityEvent event); 156 } 157 158 /** 159 * Creates a new instance that will handle callbacks from the accessibility 160 * layer on the thread of the provided looper and perform requests for privileged 161 * operations on the provided connection. 162 * 163 * @param looper The looper on which to execute accessibility callbacks. 164 * @param connection The connection for performing privileged operations. 165 * 166 * @hide 167 */ 168 public UiAutomation(Looper looper, IUiAutomationConnection connection) { 169 if (looper == null) { 170 throw new IllegalArgumentException("Looper cannot be null!"); 171 } 172 if (connection == null) { 173 throw new IllegalArgumentException("Connection cannot be null!"); 174 } 175 mUiAutomationConnection = connection; 176 mClient = new IAccessibilityServiceClientImpl(looper); 177 } 178 179 /** 180 * Connects this UiAutomation to the accessibility introspection APIs. 181 * 182 * @hide 183 */ 184 public void connect() { 185 synchronized (mLock) { 186 throwIfConnectedLocked(); 187 if (mIsConnecting) { 188 return; 189 } 190 mIsConnecting = true; 191 } 192 193 try { 194 // Calling out without a lock held. 195 mUiAutomationConnection.connect(mClient); 196 } catch (RemoteException re) { 197 throw new RuntimeException("Error while connecting UiAutomation", re); 198 } 199 200 synchronized (mLock) { 201 final long startTimeMillis = SystemClock.uptimeMillis(); 202 try { 203 while (true) { 204 if (isConnectedLocked()) { 205 break; 206 } 207 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 208 final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis; 209 if (remainingTimeMillis <= 0) { 210 throw new RuntimeException("Error while connecting UiAutomation"); 211 } 212 try { 213 mLock.wait(remainingTimeMillis); 214 } catch (InterruptedException ie) { 215 /* ignore */ 216 } 217 } 218 } finally { 219 mIsConnecting = false; 220 } 221 } 222 } 223 224 /** 225 * Disconnects this UiAutomation from the accessibility introspection APIs. 226 * 227 * @hide 228 */ 229 public void disconnect() { 230 synchronized (mLock) { 231 if (mIsConnecting) { 232 throw new IllegalStateException( 233 "Cannot call disconnect() while connecting!"); 234 } 235 throwIfNotConnectedLocked(); 236 mConnectionId = CONNECTION_ID_UNDEFINED; 237 } 238 try { 239 // Calling out without a lock held. 240 mUiAutomationConnection.disconnect(); 241 } catch (RemoteException re) { 242 throw new RuntimeException("Error while disconnecting UiAutomation", re); 243 } 244 } 245 246 /** 247 * The id of the {@link IAccessibilityInteractionConnection} for querying 248 * the screen content. This is here for legacy purposes since some tools use 249 * hidden APIs to introspect the screen. 250 * 251 * @hide 252 */ 253 public int getConnectionId() { 254 synchronized (mLock) { 255 throwIfNotConnectedLocked(); 256 return mConnectionId; 257 } 258 } 259 260 /** 261 * Sets a callback for observing the stream of {@link AccessibilityEvent}s. 262 * 263 * @param listener The callback. 264 */ 265 public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) { 266 synchronized (mLock) { 267 mOnAccessibilityEventListener = listener; 268 } 269 } 270 271 /** 272 * Performs a global action. Such an action can be performed at any moment 273 * regardless of the current application or user location in that application. 274 * For example going back, going home, opening recents, etc. 275 * 276 * @param action The action to perform. 277 * @return Whether the action was successfully performed. 278 * 279 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK 280 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME 281 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS 282 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS 283 */ 284 public final boolean performGlobalAction(int action) { 285 final IAccessibilityServiceConnection connection; 286 synchronized (mLock) { 287 throwIfNotConnectedLocked(); 288 connection = AccessibilityInteractionClient.getInstance() 289 .getConnection(mConnectionId); 290 } 291 // Calling out without a lock held. 292 if (connection != null) { 293 try { 294 return connection.performGlobalAction(action); 295 } catch (RemoteException re) { 296 Log.w(LOG_TAG, "Error while calling performGlobalAction", re); 297 } 298 } 299 return false; 300 } 301 302 /** 303 * Find the view that has the specified focus type. The search is performed 304 * across all windows. 305 * <p> 306 * <strong>Note:</strong> In order to access the windows you have to opt-in 307 * to retrieve the interactive windows by setting the 308 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 309 * Otherwise, the search will be performed only in the active window. 310 * </p> 311 * 312 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or 313 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. 314 * @return The node info of the focused view or null. 315 * 316 * @see AccessibilityNodeInfo#FOCUS_INPUT 317 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY 318 */ 319 public AccessibilityNodeInfo findFocus(int focus) { 320 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, 321 AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus); 322 } 323 324 /** 325 * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation. 326 * This method is useful if one wants to change some of the dynamically 327 * configurable properties at runtime. 328 * 329 * @return The accessibility service info. 330 * 331 * @see AccessibilityServiceInfo 332 */ 333 public final AccessibilityServiceInfo getServiceInfo() { 334 final IAccessibilityServiceConnection connection; 335 synchronized (mLock) { 336 throwIfNotConnectedLocked(); 337 connection = AccessibilityInteractionClient.getInstance() 338 .getConnection(mConnectionId); 339 } 340 // Calling out without a lock held. 341 if (connection != null) { 342 try { 343 return connection.getServiceInfo(); 344 } catch (RemoteException re) { 345 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); 346 } 347 } 348 return null; 349 } 350 351 /** 352 * Sets the {@link AccessibilityServiceInfo} that describes how this 353 * UiAutomation will be handled by the platform accessibility layer. 354 * 355 * @param info The info. 356 * 357 * @see AccessibilityServiceInfo 358 */ 359 public final void setServiceInfo(AccessibilityServiceInfo info) { 360 final IAccessibilityServiceConnection connection; 361 synchronized (mLock) { 362 throwIfNotConnectedLocked(); 363 AccessibilityInteractionClient.getInstance().clearCache(); 364 connection = AccessibilityInteractionClient.getInstance() 365 .getConnection(mConnectionId); 366 } 367 // Calling out without a lock held. 368 if (connection != null) { 369 try { 370 connection.setServiceInfo(info); 371 } catch (RemoteException re) { 372 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); 373 } 374 } 375 } 376 377 /** 378 * Gets the windows on the screen. This method returns only the windows 379 * that a sighted user can interact with, as opposed to all windows. 380 * For example, if there is a modal dialog shown and the user cannot touch 381 * anything behind it, then only the modal window will be reported 382 * (assuming it is the top one). For convenience the returned windows 383 * are ordered in a descending layer order, which is the windows that 384 * are higher in the Z-order are reported first. 385 * <p> 386 * <strong>Note:</strong> In order to access the windows you have to opt-in 387 * to retrieve the interactive windows by setting the 388 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 389 * </p> 390 * 391 * @return The windows if there are windows such, otherwise an empty list. 392 */ 393 public List<AccessibilityWindowInfo> getWindows() { 394 final int connectionId; 395 synchronized (mLock) { 396 throwIfNotConnectedLocked(); 397 connectionId = mConnectionId; 398 } 399 // Calling out without a lock held. 400 return AccessibilityInteractionClient.getInstance() 401 .getWindows(connectionId); 402 } 403 404 /** 405 * Gets the root {@link AccessibilityNodeInfo} in the active window. 406 * 407 * @return The root info. 408 */ 409 public AccessibilityNodeInfo getRootInActiveWindow() { 410 final int connectionId; 411 synchronized (mLock) { 412 throwIfNotConnectedLocked(); 413 connectionId = mConnectionId; 414 } 415 // Calling out without a lock held. 416 return AccessibilityInteractionClient.getInstance() 417 .getRootInActiveWindow(connectionId); 418 } 419 420 /** 421 * A method for injecting an arbitrary input event. 422 * <p> 423 * <strong>Note:</strong> It is caller's responsibility to recycle the event. 424 * </p> 425 * @param event The event to inject. 426 * @param sync Whether to inject the event synchronously. 427 * @return Whether event injection succeeded. 428 */ 429 public boolean injectInputEvent(InputEvent event, boolean sync) { 430 synchronized (mLock) { 431 throwIfNotConnectedLocked(); 432 } 433 try { 434 if (DEBUG) { 435 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync); 436 } 437 // Calling out without a lock held. 438 return mUiAutomationConnection.injectInputEvent(event, sync); 439 } catch (RemoteException re) { 440 Log.e(LOG_TAG, "Error while injecting input event!", re); 441 } 442 return false; 443 } 444 445 /** 446 * Sets the device rotation. A client can freeze the rotation in 447 * desired state or freeze the rotation to its current state or 448 * unfreeze the rotation (rotating the device changes its rotation 449 * state). 450 * 451 * @param rotation The desired rotation. 452 * @return Whether the rotation was set successfully. 453 * 454 * @see #ROTATION_FREEZE_0 455 * @see #ROTATION_FREEZE_90 456 * @see #ROTATION_FREEZE_180 457 * @see #ROTATION_FREEZE_270 458 * @see #ROTATION_FREEZE_CURRENT 459 * @see #ROTATION_UNFREEZE 460 */ 461 public boolean setRotation(int rotation) { 462 synchronized (mLock) { 463 throwIfNotConnectedLocked(); 464 } 465 switch (rotation) { 466 case ROTATION_FREEZE_0: 467 case ROTATION_FREEZE_90: 468 case ROTATION_FREEZE_180: 469 case ROTATION_FREEZE_270: 470 case ROTATION_UNFREEZE: 471 case ROTATION_FREEZE_CURRENT: { 472 try { 473 // Calling out without a lock held. 474 mUiAutomationConnection.setRotation(rotation); 475 return true; 476 } catch (RemoteException re) { 477 Log.e(LOG_TAG, "Error while setting rotation!", re); 478 } 479 } return false; 480 default: { 481 throw new IllegalArgumentException("Invalid rotation."); 482 } 483 } 484 } 485 486 /** 487 * Executes a command and waits for a specific accessibility event up to a 488 * given wait timeout. To detect a sequence of events one can implement a 489 * filter that keeps track of seen events of the expected sequence and 490 * returns true after the last event of that sequence is received. 491 * <p> 492 * <strong>Note:</strong> It is caller's responsibility to recycle the returned event. 493 * </p> 494 * @param command The command to execute. 495 * @param filter Filter that recognizes the expected event. 496 * @param timeoutMillis The wait timeout in milliseconds. 497 * 498 * @throws TimeoutException If the expected event is not received within the timeout. 499 */ 500 public AccessibilityEvent executeAndWaitForEvent(Runnable command, 501 AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException { 502 // Acquire the lock and prepare for receiving events. 503 synchronized (mLock) { 504 throwIfNotConnectedLocked(); 505 mEventQueue.clear(); 506 // Prepare to wait for an event. 507 mWaitingForEventDelivery = true; 508 } 509 510 // Note: We have to release the lock since calling out with this lock held 511 // can bite. We will correctly filter out events from other interactions, 512 // so starting to collect events before running the action is just fine. 513 514 // We will ignore events from previous interactions. 515 final long executionStartTimeMillis = SystemClock.uptimeMillis(); 516 // Execute the command *without* the lock being held. 517 command.run(); 518 519 // Acquire the lock and wait for the event. 520 synchronized (mLock) { 521 try { 522 // Wait for the event. 523 final long startTimeMillis = SystemClock.uptimeMillis(); 524 while (true) { 525 // Drain the event queue 526 while (!mEventQueue.isEmpty()) { 527 AccessibilityEvent event = mEventQueue.remove(0); 528 // Ignore events from previous interactions. 529 if (event.getEventTime() < executionStartTimeMillis) { 530 continue; 531 } 532 if (filter.accept(event)) { 533 return event; 534 } 535 event.recycle(); 536 } 537 // Check if timed out and if not wait. 538 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 539 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 540 if (remainingTimeMillis <= 0) { 541 throw new TimeoutException("Expected event not received within: " 542 + timeoutMillis + " ms."); 543 } 544 try { 545 mLock.wait(remainingTimeMillis); 546 } catch (InterruptedException ie) { 547 /* ignore */ 548 } 549 } 550 } finally { 551 mWaitingForEventDelivery = false; 552 mEventQueue.clear(); 553 mLock.notifyAll(); 554 } 555 } 556 } 557 558 /** 559 * Waits for the accessibility event stream to become idle, which is not to 560 * have received an accessibility event within <code>idleTimeoutMillis</code>. 561 * The total time spent to wait for an idle accessibility event stream is bounded 562 * by the <code>globalTimeoutMillis</code>. 563 * 564 * @param idleTimeoutMillis The timeout in milliseconds between two events 565 * to consider the device idle. 566 * @param globalTimeoutMillis The maximal global timeout in milliseconds in 567 * which to wait for an idle state. 568 * 569 * @throws TimeoutException If no idle state was detected within 570 * <code>globalTimeoutMillis.</code> 571 */ 572 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis) 573 throws TimeoutException { 574 synchronized (mLock) { 575 throwIfNotConnectedLocked(); 576 577 final long startTimeMillis = SystemClock.uptimeMillis(); 578 if (mLastEventTimeMillis <= 0) { 579 mLastEventTimeMillis = startTimeMillis; 580 } 581 582 while (true) { 583 final long currentTimeMillis = SystemClock.uptimeMillis(); 584 // Did we get idle state within the global timeout? 585 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis; 586 final long remainingGlobalTimeMillis = 587 globalTimeoutMillis - elapsedGlobalTimeMillis; 588 if (remainingGlobalTimeMillis <= 0) { 589 throw new TimeoutException("No idle state with idle timeout: " 590 + idleTimeoutMillis + " within global timeout: " 591 + globalTimeoutMillis); 592 } 593 // Did we get an idle state within the idle timeout? 594 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis; 595 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis; 596 if (remainingIdleTimeMillis <= 0) { 597 return; 598 } 599 try { 600 mLock.wait(remainingIdleTimeMillis); 601 } catch (InterruptedException ie) { 602 /* ignore */ 603 } 604 } 605 } 606 } 607 608 /** 609 * Takes a screenshot. 610 * 611 * @return The screenshot bitmap on success, null otherwise. 612 */ 613 public Bitmap takeScreenshot() { 614 synchronized (mLock) { 615 throwIfNotConnectedLocked(); 616 } 617 Display display = DisplayManagerGlobal.getInstance() 618 .getRealDisplay(Display.DEFAULT_DISPLAY); 619 Point displaySize = new Point(); 620 display.getRealSize(displaySize); 621 final int displayWidth = displaySize.x; 622 final int displayHeight = displaySize.y; 623 624 final float screenshotWidth; 625 final float screenshotHeight; 626 627 final int rotation = display.getRotation(); 628 switch (rotation) { 629 case ROTATION_FREEZE_0: { 630 screenshotWidth = displayWidth; 631 screenshotHeight = displayHeight; 632 } break; 633 case ROTATION_FREEZE_90: { 634 screenshotWidth = displayHeight; 635 screenshotHeight = displayWidth; 636 } break; 637 case ROTATION_FREEZE_180: { 638 screenshotWidth = displayWidth; 639 screenshotHeight = displayHeight; 640 } break; 641 case ROTATION_FREEZE_270: { 642 screenshotWidth = displayHeight; 643 screenshotHeight = displayWidth; 644 } break; 645 default: { 646 throw new IllegalArgumentException("Invalid rotation: " 647 + rotation); 648 } 649 } 650 651 // Take the screenshot 652 Bitmap screenShot = null; 653 try { 654 // Calling out without a lock held. 655 screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth, 656 (int) screenshotHeight); 657 if (screenShot == null) { 658 return null; 659 } 660 } catch (RemoteException re) { 661 Log.e(LOG_TAG, "Error while taking screnshot!", re); 662 return null; 663 } 664 665 // Rotate the screenshot to the current orientation 666 if (rotation != ROTATION_FREEZE_0) { 667 Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight, 668 Bitmap.Config.ARGB_8888); 669 Canvas canvas = new Canvas(unrotatedScreenShot); 670 canvas.translate(unrotatedScreenShot.getWidth() / 2, 671 unrotatedScreenShot.getHeight() / 2); 672 canvas.rotate(getDegreesForRotation(rotation)); 673 canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2); 674 canvas.drawBitmap(screenShot, 0, 0, null); 675 canvas.setBitmap(null); 676 screenShot.recycle(); 677 screenShot = unrotatedScreenShot; 678 } 679 680 // Optimization 681 screenShot.setHasAlpha(false); 682 683 return screenShot; 684 } 685 686 /** 687 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether 688 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing 689 * potentially undesirable actions such as calling 911 or posting on public forums etc. 690 * 691 * @param enable whether to run in a "monkey" mode or not. Default is not. 692 * @see {@link android.app.ActivityManager#isUserAMonkey()} 693 */ 694 public void setRunAsMonkey(boolean enable) { 695 synchronized (mLock) { 696 throwIfNotConnectedLocked(); 697 } 698 try { 699 ActivityManagerNative.getDefault().setUserIsMonkey(enable); 700 } catch (RemoteException re) { 701 Log.e(LOG_TAG, "Error while setting run as monkey!", re); 702 } 703 } 704 705 /** 706 * Clears the frame statistics for the content of a given window. These 707 * statistics contain information about the most recently rendered content 708 * frames. 709 * 710 * @param windowId The window id. 711 * @return Whether the window is present and its frame statistics 712 * were cleared. 713 * 714 * @see android.view.WindowContentFrameStats 715 * @see #getWindowContentFrameStats(int) 716 * @see #getWindows() 717 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 718 */ 719 public boolean clearWindowContentFrameStats(int windowId) { 720 synchronized (mLock) { 721 throwIfNotConnectedLocked(); 722 } 723 try { 724 if (DEBUG) { 725 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId); 726 } 727 // Calling out without a lock held. 728 return mUiAutomationConnection.clearWindowContentFrameStats(windowId); 729 } catch (RemoteException re) { 730 Log.e(LOG_TAG, "Error clearing window content frame stats!", re); 731 } 732 return false; 733 } 734 735 /** 736 * Gets the frame statistics for a given window. These statistics contain 737 * information about the most recently rendered content frames. 738 * <p> 739 * A typical usage requires clearing the window frame statistics via {@link 740 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and 741 * finally getting the window frame statistics via calling this method. 742 * </p> 743 * <pre> 744 * // Assume we have at least one window. 745 * final int windowId = getWindows().get(0).getId(); 746 * 747 * // Start with a clean slate. 748 * uiAutimation.clearWindowContentFrameStats(windowId); 749 * 750 * // Do stuff with the UI. 751 * 752 * // Get the frame statistics. 753 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId); 754 * </pre> 755 * 756 * @param windowId The window id. 757 * @return The window frame statistics, or null if the window is not present. 758 * 759 * @see android.view.WindowContentFrameStats 760 * @see #clearWindowContentFrameStats(int) 761 * @see #getWindows() 762 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 763 */ 764 public WindowContentFrameStats getWindowContentFrameStats(int windowId) { 765 synchronized (mLock) { 766 throwIfNotConnectedLocked(); 767 } 768 try { 769 if (DEBUG) { 770 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId); 771 } 772 // Calling out without a lock held. 773 return mUiAutomationConnection.getWindowContentFrameStats(windowId); 774 } catch (RemoteException re) { 775 Log.e(LOG_TAG, "Error getting window content frame stats!", re); 776 } 777 return null; 778 } 779 780 /** 781 * Clears the window animation rendering statistics. These statistics contain 782 * information about the most recently rendered window animation frames, i.e. 783 * for window transition animations. 784 * 785 * @see android.view.WindowAnimationFrameStats 786 * @see #getWindowAnimationFrameStats() 787 * @see android.R.styleable#WindowAnimation 788 */ 789 public void clearWindowAnimationFrameStats() { 790 synchronized (mLock) { 791 throwIfNotConnectedLocked(); 792 } 793 try { 794 if (DEBUG) { 795 Log.i(LOG_TAG, "Clearing window animation frame stats"); 796 } 797 // Calling out without a lock held. 798 mUiAutomationConnection.clearWindowAnimationFrameStats(); 799 } catch (RemoteException re) { 800 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re); 801 } 802 } 803 804 /** 805 * Gets the window animation frame statistics. These statistics contain 806 * information about the most recently rendered window animation frames, i.e. 807 * for window transition animations. 808 * 809 * <p> 810 * A typical usage requires clearing the window animation frame statistics via 811 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes 812 * a window transition which uses a window animation and finally getting the window 813 * animation frame statistics by calling this method. 814 * </p> 815 * <pre> 816 * // Start with a clean slate. 817 * uiAutimation.clearWindowAnimationFrameStats(); 818 * 819 * // Do stuff to trigger a window transition. 820 * 821 * // Get the frame statistics. 822 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats(); 823 * </pre> 824 * 825 * @return The window animation frame statistics. 826 * 827 * @see android.view.WindowAnimationFrameStats 828 * @see #clearWindowAnimationFrameStats() 829 * @see android.R.styleable#WindowAnimation 830 */ 831 public WindowAnimationFrameStats getWindowAnimationFrameStats() { 832 synchronized (mLock) { 833 throwIfNotConnectedLocked(); 834 } 835 try { 836 if (DEBUG) { 837 Log.i(LOG_TAG, "Getting window animation frame stats"); 838 } 839 // Calling out without a lock held. 840 return mUiAutomationConnection.getWindowAnimationFrameStats(); 841 } catch (RemoteException re) { 842 Log.e(LOG_TAG, "Error getting window animation frame stats!", re); 843 } 844 return null; 845 } 846 847 /** 848 * Executes a shell command. This method returs a file descriptor that points 849 * to the standard output stream. The command execution is similar to running 850 * "adb shell <command>" from a host connected to the device. 851 * <p> 852 * <strong>Note:</strong> It is your responsibility to close the retunred file 853 * descriptor once you are done reading. 854 * </p> 855 * 856 * @param command The command to execute. 857 * @return A file descriptor to the standard output stream. 858 */ 859 public ParcelFileDescriptor executeShellCommand(String command) { 860 synchronized (mLock) { 861 throwIfNotConnectedLocked(); 862 } 863 864 ParcelFileDescriptor source = null; 865 ParcelFileDescriptor sink = null; 866 867 try { 868 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 869 source = pipe[0]; 870 sink = pipe[1]; 871 872 // Calling out without a lock held. 873 mUiAutomationConnection.executeShellCommand(command, sink); 874 } catch (IOException ioe) { 875 Log.e(LOG_TAG, "Error executing shell command!", ioe); 876 } catch (RemoteException re) { 877 Log.e(LOG_TAG, "Error executing shell command!", re); 878 } finally { 879 IoUtils.closeQuietly(sink); 880 } 881 882 return source; 883 } 884 885 private static float getDegreesForRotation(int value) { 886 switch (value) { 887 case Surface.ROTATION_90: { 888 return 360f - 90f; 889 } 890 case Surface.ROTATION_180: { 891 return 360f - 180f; 892 } 893 case Surface.ROTATION_270: { 894 return 360f - 270f; 895 } default: { 896 return 0; 897 } 898 } 899 } 900 901 private boolean isConnectedLocked() { 902 return mConnectionId != CONNECTION_ID_UNDEFINED; 903 } 904 905 private void throwIfConnectedLocked() { 906 if (mConnectionId != CONNECTION_ID_UNDEFINED) { 907 throw new IllegalStateException("UiAutomation not connected!"); 908 } 909 } 910 911 private void throwIfNotConnectedLocked() { 912 if (!isConnectedLocked()) { 913 throw new IllegalStateException("UiAutomation not connected!"); 914 } 915 } 916 917 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { 918 919 public IAccessibilityServiceClientImpl(Looper looper) { 920 super(null, looper, new Callbacks() { 921 @Override 922 public void onSetConnectionId(int connectionId) { 923 synchronized (mLock) { 924 mConnectionId = connectionId; 925 mLock.notifyAll(); 926 } 927 } 928 929 @Override 930 public void onServiceConnected() { 931 /* do nothing */ 932 } 933 934 @Override 935 public void onInterrupt() { 936 /* do nothing */ 937 } 938 939 @Override 940 public boolean onGesture(int gestureId) { 941 /* do nothing */ 942 return false; 943 } 944 945 @Override 946 public void onAccessibilityEvent(AccessibilityEvent event) { 947 synchronized (mLock) { 948 mLastEventTimeMillis = event.getEventTime(); 949 if (mWaitingForEventDelivery) { 950 mEventQueue.add(AccessibilityEvent.obtain(event)); 951 } 952 mLock.notifyAll(); 953 } 954 // Calling out only without a lock held. 955 final OnAccessibilityEventListener listener = mOnAccessibilityEventListener; 956 if (listener != null) { 957 listener.onAccessibilityEvent(AccessibilityEvent.obtain(event)); 958 } 959 } 960 961 @Override 962 public boolean onKeyEvent(KeyEvent event) { 963 return false; 964 } 965 }); 966 } 967 } 968 } 969