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 AccessibilityWindowInfo.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 try { 584 // Wait for the event. 585 final long startTimeMillis = SystemClock.uptimeMillis(); 586 while (true) { 587 List<AccessibilityEvent> localEvents = new ArrayList<>(); 588 synchronized (mLock) { 589 localEvents.addAll(mEventQueue); 590 mEventQueue.clear(); 591 } 592 // Drain the event queue 593 while (!localEvents.isEmpty()) { 594 AccessibilityEvent event = localEvents.remove(0); 595 // Ignore events from previous interactions. 596 if (event.getEventTime() < executionStartTimeMillis) { 597 continue; 598 } 599 if (filter.accept(event)) { 600 return event; 601 } 602 event.recycle(); 603 } 604 // Check if timed out and if not wait. 605 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 606 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 607 if (remainingTimeMillis <= 0) { 608 throw new TimeoutException("Expected event not received within: " 609 + timeoutMillis + " ms."); 610 } 611 synchronized (mLock) { 612 if (mEventQueue.isEmpty()) { 613 try { 614 mLock.wait(remainingTimeMillis); 615 } catch (InterruptedException ie) { 616 /* ignore */ 617 } 618 } 619 } 620 } 621 } finally { 622 synchronized (mLock) { 623 mWaitingForEventDelivery = false; 624 mEventQueue.clear(); 625 mLock.notifyAll(); 626 } 627 } 628 } 629 630 /** 631 * Waits for the accessibility event stream to become idle, which is not to 632 * have received an accessibility event within <code>idleTimeoutMillis</code>. 633 * The total time spent to wait for an idle accessibility event stream is bounded 634 * by the <code>globalTimeoutMillis</code>. 635 * 636 * @param idleTimeoutMillis The timeout in milliseconds between two events 637 * to consider the device idle. 638 * @param globalTimeoutMillis The maximal global timeout in milliseconds in 639 * which to wait for an idle state. 640 * 641 * @throws TimeoutException If no idle state was detected within 642 * <code>globalTimeoutMillis.</code> 643 */ waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)644 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis) 645 throws TimeoutException { 646 synchronized (mLock) { 647 throwIfNotConnectedLocked(); 648 649 final long startTimeMillis = SystemClock.uptimeMillis(); 650 if (mLastEventTimeMillis <= 0) { 651 mLastEventTimeMillis = startTimeMillis; 652 } 653 654 while (true) { 655 final long currentTimeMillis = SystemClock.uptimeMillis(); 656 // Did we get idle state within the global timeout? 657 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis; 658 final long remainingGlobalTimeMillis = 659 globalTimeoutMillis - elapsedGlobalTimeMillis; 660 if (remainingGlobalTimeMillis <= 0) { 661 throw new TimeoutException("No idle state with idle timeout: " 662 + idleTimeoutMillis + " within global timeout: " 663 + globalTimeoutMillis); 664 } 665 // Did we get an idle state within the idle timeout? 666 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis; 667 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis; 668 if (remainingIdleTimeMillis <= 0) { 669 return; 670 } 671 try { 672 mLock.wait(remainingIdleTimeMillis); 673 } catch (InterruptedException ie) { 674 /* ignore */ 675 } 676 } 677 } 678 } 679 680 /** 681 * Takes a screenshot. 682 * 683 * @return The screenshot bitmap on success, null otherwise. 684 */ takeScreenshot()685 public Bitmap takeScreenshot() { 686 synchronized (mLock) { 687 throwIfNotConnectedLocked(); 688 } 689 Display display = DisplayManagerGlobal.getInstance() 690 .getRealDisplay(Display.DEFAULT_DISPLAY); 691 Point displaySize = new Point(); 692 display.getRealSize(displaySize); 693 final int displayWidth = displaySize.x; 694 final int displayHeight = displaySize.y; 695 696 final float screenshotWidth; 697 final float screenshotHeight; 698 699 final int rotation = display.getRotation(); 700 switch (rotation) { 701 case ROTATION_FREEZE_0: { 702 screenshotWidth = displayWidth; 703 screenshotHeight = displayHeight; 704 } break; 705 case ROTATION_FREEZE_90: { 706 screenshotWidth = displayHeight; 707 screenshotHeight = displayWidth; 708 } break; 709 case ROTATION_FREEZE_180: { 710 screenshotWidth = displayWidth; 711 screenshotHeight = displayHeight; 712 } break; 713 case ROTATION_FREEZE_270: { 714 screenshotWidth = displayHeight; 715 screenshotHeight = displayWidth; 716 } break; 717 default: { 718 throw new IllegalArgumentException("Invalid rotation: " 719 + rotation); 720 } 721 } 722 723 // Take the screenshot 724 Bitmap screenShot = null; 725 try { 726 // Calling out without a lock held. 727 screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth, 728 (int) screenshotHeight); 729 if (screenShot == null) { 730 return null; 731 } 732 } catch (RemoteException re) { 733 Log.e(LOG_TAG, "Error while taking screnshot!", re); 734 return null; 735 } 736 737 // Rotate the screenshot to the current orientation 738 if (rotation != ROTATION_FREEZE_0) { 739 Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight, 740 Bitmap.Config.ARGB_8888); 741 Canvas canvas = new Canvas(unrotatedScreenShot); 742 canvas.translate(unrotatedScreenShot.getWidth() / 2, 743 unrotatedScreenShot.getHeight() / 2); 744 canvas.rotate(getDegreesForRotation(rotation)); 745 canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2); 746 canvas.drawBitmap(screenShot, 0, 0, null); 747 canvas.setBitmap(null); 748 screenShot.recycle(); 749 screenShot = unrotatedScreenShot; 750 } 751 752 // Optimization 753 screenShot.setHasAlpha(false); 754 755 return screenShot; 756 } 757 758 /** 759 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether 760 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing 761 * potentially undesirable actions such as calling 911 or posting on public forums etc. 762 * 763 * @param enable whether to run in a "monkey" mode or not. Default is not. 764 * @see ActivityManager#isUserAMonkey() 765 */ setRunAsMonkey(boolean enable)766 public void setRunAsMonkey(boolean enable) { 767 synchronized (mLock) { 768 throwIfNotConnectedLocked(); 769 } 770 try { 771 ActivityManager.getService().setUserIsMonkey(enable); 772 } catch (RemoteException re) { 773 Log.e(LOG_TAG, "Error while setting run as monkey!", re); 774 } 775 } 776 777 /** 778 * Clears the frame statistics for the content of a given window. These 779 * statistics contain information about the most recently rendered content 780 * frames. 781 * 782 * @param windowId The window id. 783 * @return Whether the window is present and its frame statistics 784 * were cleared. 785 * 786 * @see android.view.WindowContentFrameStats 787 * @see #getWindowContentFrameStats(int) 788 * @see #getWindows() 789 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 790 */ clearWindowContentFrameStats(int windowId)791 public boolean clearWindowContentFrameStats(int windowId) { 792 synchronized (mLock) { 793 throwIfNotConnectedLocked(); 794 } 795 try { 796 if (DEBUG) { 797 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId); 798 } 799 // Calling out without a lock held. 800 return mUiAutomationConnection.clearWindowContentFrameStats(windowId); 801 } catch (RemoteException re) { 802 Log.e(LOG_TAG, "Error clearing window content frame stats!", re); 803 } 804 return false; 805 } 806 807 /** 808 * Gets the frame statistics for a given window. These statistics contain 809 * information about the most recently rendered content frames. 810 * <p> 811 * A typical usage requires clearing the window frame statistics via {@link 812 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and 813 * finally getting the window frame statistics via calling this method. 814 * </p> 815 * <pre> 816 * // Assume we have at least one window. 817 * final int windowId = getWindows().get(0).getId(); 818 * 819 * // Start with a clean slate. 820 * uiAutimation.clearWindowContentFrameStats(windowId); 821 * 822 * // Do stuff with the UI. 823 * 824 * // Get the frame statistics. 825 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId); 826 * </pre> 827 * 828 * @param windowId The window id. 829 * @return The window frame statistics, or null if the window is not present. 830 * 831 * @see android.view.WindowContentFrameStats 832 * @see #clearWindowContentFrameStats(int) 833 * @see #getWindows() 834 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 835 */ getWindowContentFrameStats(int windowId)836 public WindowContentFrameStats getWindowContentFrameStats(int windowId) { 837 synchronized (mLock) { 838 throwIfNotConnectedLocked(); 839 } 840 try { 841 if (DEBUG) { 842 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId); 843 } 844 // Calling out without a lock held. 845 return mUiAutomationConnection.getWindowContentFrameStats(windowId); 846 } catch (RemoteException re) { 847 Log.e(LOG_TAG, "Error getting window content frame stats!", re); 848 } 849 return null; 850 } 851 852 /** 853 * Clears the window animation rendering statistics. These statistics contain 854 * information about the most recently rendered window animation frames, i.e. 855 * for window transition animations. 856 * 857 * @see android.view.WindowAnimationFrameStats 858 * @see #getWindowAnimationFrameStats() 859 * @see android.R.styleable#WindowAnimation 860 */ clearWindowAnimationFrameStats()861 public void clearWindowAnimationFrameStats() { 862 synchronized (mLock) { 863 throwIfNotConnectedLocked(); 864 } 865 try { 866 if (DEBUG) { 867 Log.i(LOG_TAG, "Clearing window animation frame stats"); 868 } 869 // Calling out without a lock held. 870 mUiAutomationConnection.clearWindowAnimationFrameStats(); 871 } catch (RemoteException re) { 872 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re); 873 } 874 } 875 876 /** 877 * Gets the window animation frame statistics. These statistics contain 878 * information about the most recently rendered window animation frames, i.e. 879 * for window transition animations. 880 * 881 * <p> 882 * A typical usage requires clearing the window animation frame statistics via 883 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes 884 * a window transition which uses a window animation and finally getting the window 885 * animation frame statistics by calling this method. 886 * </p> 887 * <pre> 888 * // Start with a clean slate. 889 * uiAutimation.clearWindowAnimationFrameStats(); 890 * 891 * // Do stuff to trigger a window transition. 892 * 893 * // Get the frame statistics. 894 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats(); 895 * </pre> 896 * 897 * @return The window animation frame statistics. 898 * 899 * @see android.view.WindowAnimationFrameStats 900 * @see #clearWindowAnimationFrameStats() 901 * @see android.R.styleable#WindowAnimation 902 */ getWindowAnimationFrameStats()903 public WindowAnimationFrameStats getWindowAnimationFrameStats() { 904 synchronized (mLock) { 905 throwIfNotConnectedLocked(); 906 } 907 try { 908 if (DEBUG) { 909 Log.i(LOG_TAG, "Getting window animation frame stats"); 910 } 911 // Calling out without a lock held. 912 return mUiAutomationConnection.getWindowAnimationFrameStats(); 913 } catch (RemoteException re) { 914 Log.e(LOG_TAG, "Error getting window animation frame stats!", re); 915 } 916 return null; 917 } 918 919 /** 920 * Grants a runtime permission to a package for a user. 921 * @param packageName The package to which to grant. 922 * @param permission The permission to grant. 923 * @return Whether granting succeeded. 924 * 925 * @hide 926 */ 927 @TestApi grantRuntimePermission(String packageName, String permission, UserHandle userHandle)928 public boolean grantRuntimePermission(String packageName, String permission, 929 UserHandle userHandle) { 930 synchronized (mLock) { 931 throwIfNotConnectedLocked(); 932 } 933 try { 934 if (DEBUG) { 935 Log.i(LOG_TAG, "Granting runtime permission"); 936 } 937 // Calling out without a lock held. 938 mUiAutomationConnection.grantRuntimePermission(packageName, 939 permission, userHandle.getIdentifier()); 940 // TODO: The package manager API should return boolean. 941 return true; 942 } catch (RemoteException re) { 943 Log.e(LOG_TAG, "Error granting runtime permission", re); 944 } 945 return false; 946 } 947 948 /** 949 * Revokes a runtime permission from a package for a user. 950 * @param packageName The package from which to revoke. 951 * @param permission The permission to revoke. 952 * @return Whether revoking succeeded. 953 * 954 * @hide 955 */ 956 @TestApi revokeRuntimePermission(String packageName, String permission, UserHandle userHandle)957 public boolean revokeRuntimePermission(String packageName, String permission, 958 UserHandle userHandle) { 959 synchronized (mLock) { 960 throwIfNotConnectedLocked(); 961 } 962 try { 963 if (DEBUG) { 964 Log.i(LOG_TAG, "Revoking runtime permission"); 965 } 966 // Calling out without a lock held. 967 mUiAutomationConnection.revokeRuntimePermission(packageName, 968 permission, userHandle.getIdentifier()); 969 // TODO: The package manager API should return boolean. 970 return true; 971 } catch (RemoteException re) { 972 Log.e(LOG_TAG, "Error revoking runtime permission", re); 973 } 974 return false; 975 } 976 977 /** 978 * Executes a shell command. This method returs a file descriptor that points 979 * to the standard output stream. The command execution is similar to running 980 * "adb shell <command>" from a host connected to the device. 981 * <p> 982 * <strong>Note:</strong> It is your responsibility to close the retunred file 983 * descriptor once you are done reading. 984 * </p> 985 * 986 * @param command The command to execute. 987 * @return A file descriptor to the standard output stream. 988 */ executeShellCommand(String command)989 public ParcelFileDescriptor executeShellCommand(String command) { 990 synchronized (mLock) { 991 throwIfNotConnectedLocked(); 992 } 993 994 ParcelFileDescriptor source = null; 995 ParcelFileDescriptor sink = null; 996 997 try { 998 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 999 source = pipe[0]; 1000 sink = pipe[1]; 1001 1002 // Calling out without a lock held. 1003 mUiAutomationConnection.executeShellCommand(command, sink); 1004 } catch (IOException ioe) { 1005 Log.e(LOG_TAG, "Error executing shell command!", ioe); 1006 } catch (RemoteException re) { 1007 Log.e(LOG_TAG, "Error executing shell command!", re); 1008 } finally { 1009 IoUtils.closeQuietly(sink); 1010 } 1011 1012 return source; 1013 } 1014 getDegreesForRotation(int value)1015 private static float getDegreesForRotation(int value) { 1016 switch (value) { 1017 case Surface.ROTATION_90: { 1018 return 360f - 90f; 1019 } 1020 case Surface.ROTATION_180: { 1021 return 360f - 180f; 1022 } 1023 case Surface.ROTATION_270: { 1024 return 360f - 270f; 1025 } default: { 1026 return 0; 1027 } 1028 } 1029 } 1030 isConnectedLocked()1031 private boolean isConnectedLocked() { 1032 return mConnectionId != CONNECTION_ID_UNDEFINED; 1033 } 1034 throwIfConnectedLocked()1035 private void throwIfConnectedLocked() { 1036 if (mConnectionId != CONNECTION_ID_UNDEFINED) { 1037 throw new IllegalStateException("UiAutomation not connected!"); 1038 } 1039 } 1040 throwIfNotConnectedLocked()1041 private void throwIfNotConnectedLocked() { 1042 if (!isConnectedLocked()) { 1043 throw new IllegalStateException("UiAutomation not connected!"); 1044 } 1045 } 1046 1047 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { 1048 IAccessibilityServiceClientImpl(Looper looper)1049 public IAccessibilityServiceClientImpl(Looper looper) { 1050 super(null, looper, new Callbacks() { 1051 @Override 1052 public void init(int connectionId, IBinder windowToken) { 1053 synchronized (mLock) { 1054 mConnectionId = connectionId; 1055 mLock.notifyAll(); 1056 } 1057 } 1058 1059 @Override 1060 public void onServiceConnected() { 1061 /* do nothing */ 1062 } 1063 1064 @Override 1065 public void onInterrupt() { 1066 /* do nothing */ 1067 } 1068 1069 @Override 1070 public boolean onGesture(int gestureId) { 1071 /* do nothing */ 1072 return false; 1073 } 1074 1075 @Override 1076 public void onAccessibilityEvent(AccessibilityEvent event) { 1077 synchronized (mLock) { 1078 mLastEventTimeMillis = event.getEventTime(); 1079 if (mWaitingForEventDelivery) { 1080 mEventQueue.add(AccessibilityEvent.obtain(event)); 1081 } 1082 mLock.notifyAll(); 1083 } 1084 // Calling out only without a lock held. 1085 final OnAccessibilityEventListener listener = mOnAccessibilityEventListener; 1086 if (listener != null) { 1087 listener.onAccessibilityEvent(AccessibilityEvent.obtain(event)); 1088 } 1089 } 1090 1091 @Override 1092 public boolean onKeyEvent(KeyEvent event) { 1093 return false; 1094 } 1095 1096 @Override 1097 public void onMagnificationChanged(@NonNull Region region, 1098 float scale, float centerX, float centerY) { 1099 /* do nothing */ 1100 } 1101 1102 @Override 1103 public void onSoftKeyboardShowModeChanged(int showMode) { 1104 /* do nothing */ 1105 } 1106 1107 @Override 1108 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) { 1109 /* do nothing */ 1110 } 1111 1112 @Override 1113 public void onFingerprintCapturingGesturesChanged(boolean active) { 1114 /* do nothing */ 1115 } 1116 1117 @Override 1118 public void onFingerprintGesture(int gesture) { 1119 /* do nothing */ 1120 } 1121 1122 @Override 1123 public void onAccessibilityButtonClicked() { 1124 /* do nothing */ 1125 } 1126 1127 @Override 1128 public void onAccessibilityButtonAvailabilityChanged(boolean available) { 1129 /* do nothing */ 1130 } 1131 }); 1132 } 1133 } 1134 } 1135