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