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