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