UiAutomation.java revision f3e5d1d483231d615f5e77032f787fcd8047488b
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 17package android.app; 18 19import android.accessibilityservice.AccessibilityService.Callbacks; 20import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper; 21import android.accessibilityservice.AccessibilityServiceInfo; 22import android.accessibilityservice.IAccessibilityServiceClient; 23import android.accessibilityservice.IAccessibilityServiceConnection; 24import android.annotation.NonNull; 25import android.annotation.TestApi; 26import android.graphics.Bitmap; 27import android.graphics.Canvas; 28import android.graphics.Point; 29import android.graphics.Region; 30import android.hardware.display.DisplayManagerGlobal; 31import android.os.IBinder; 32import android.os.Looper; 33import android.os.ParcelFileDescriptor; 34import android.os.RemoteException; 35import android.os.SystemClock; 36import android.os.UserHandle; 37import android.util.Log; 38import android.view.Display; 39import android.view.InputEvent; 40import android.view.KeyEvent; 41import android.view.Surface; 42import android.view.WindowAnimationFrameStats; 43import android.view.WindowContentFrameStats; 44import android.view.accessibility.AccessibilityEvent; 45import android.view.accessibility.AccessibilityInteractionClient; 46import android.view.accessibility.AccessibilityNodeInfo; 47import android.view.accessibility.AccessibilityWindowInfo; 48import android.view.accessibility.IAccessibilityInteractionConnection; 49import libcore.io.IoUtils; 50 51import java.io.IOException; 52import java.util.ArrayList; 53import java.util.List; 54import 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 */ 80public 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 260 public int getFlags() { 261 return mFlags; 262 } 263 264 /** 265 * Disconnects this UiAutomation from the accessibility introspection APIs. 266 * 267 * @hide 268 */ 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 */ 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 */ 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 */ 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 public void destroy() { 327 disconnect(); 328 mIsDestroyed = true; 329 } 330 331 /** 332 * Performs a global action. Such an action can be performed at any moment 333 * regardless of the current application or user location in that application. 334 * For example going back, going home, opening recents, etc. 335 * 336 * @param action The action to perform. 337 * @return Whether the action was successfully performed. 338 * 339 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK 340 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME 341 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS 342 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS 343 */ 344 public final boolean performGlobalAction(int action) { 345 final IAccessibilityServiceConnection connection; 346 synchronized (mLock) { 347 throwIfNotConnectedLocked(); 348 connection = AccessibilityInteractionClient.getInstance() 349 .getConnection(mConnectionId); 350 } 351 // Calling out without a lock held. 352 if (connection != null) { 353 try { 354 return connection.performGlobalAction(action); 355 } catch (RemoteException re) { 356 Log.w(LOG_TAG, "Error while calling performGlobalAction", re); 357 } 358 } 359 return false; 360 } 361 362 /** 363 * Find the view that has the specified focus type. The search is performed 364 * across all windows. 365 * <p> 366 * <strong>Note:</strong> In order to access the windows you have to opt-in 367 * to retrieve the interactive windows by setting the 368 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 369 * Otherwise, the search will be performed only in the active window. 370 * </p> 371 * 372 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or 373 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. 374 * @return The node info of the focused view or null. 375 * 376 * @see AccessibilityNodeInfo#FOCUS_INPUT 377 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY 378 */ 379 public AccessibilityNodeInfo findFocus(int focus) { 380 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, 381 AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus); 382 } 383 384 /** 385 * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation. 386 * This method is useful if one wants to change some of the dynamically 387 * configurable properties at runtime. 388 * 389 * @return The accessibility service info. 390 * 391 * @see AccessibilityServiceInfo 392 */ 393 public final AccessibilityServiceInfo getServiceInfo() { 394 final IAccessibilityServiceConnection connection; 395 synchronized (mLock) { 396 throwIfNotConnectedLocked(); 397 connection = AccessibilityInteractionClient.getInstance() 398 .getConnection(mConnectionId); 399 } 400 // Calling out without a lock held. 401 if (connection != null) { 402 try { 403 return connection.getServiceInfo(); 404 } catch (RemoteException re) { 405 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); 406 } 407 } 408 return null; 409 } 410 411 /** 412 * Sets the {@link AccessibilityServiceInfo} that describes how this 413 * UiAutomation will be handled by the platform accessibility layer. 414 * 415 * @param info The info. 416 * 417 * @see AccessibilityServiceInfo 418 */ 419 public final void setServiceInfo(AccessibilityServiceInfo info) { 420 final IAccessibilityServiceConnection connection; 421 synchronized (mLock) { 422 throwIfNotConnectedLocked(); 423 AccessibilityInteractionClient.getInstance().clearCache(); 424 connection = AccessibilityInteractionClient.getInstance() 425 .getConnection(mConnectionId); 426 } 427 // Calling out without a lock held. 428 if (connection != null) { 429 try { 430 connection.setServiceInfo(info); 431 } catch (RemoteException re) { 432 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); 433 } 434 } 435 } 436 437 /** 438 * Gets the windows on the screen. This method returns only the windows 439 * that a sighted user can interact with, as opposed to all windows. 440 * For example, if there is a modal dialog shown and the user cannot touch 441 * anything behind it, then only the modal window will be reported 442 * (assuming it is the top one). For convenience the returned windows 443 * are ordered in a descending layer order, which is the windows that 444 * are higher in the Z-order are reported first. 445 * <p> 446 * <strong>Note:</strong> In order to access the windows you have to opt-in 447 * to retrieve the interactive windows by setting the 448 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 449 * </p> 450 * 451 * @return The windows if there are windows such, otherwise an empty list. 452 */ 453 public List<AccessibilityWindowInfo> getWindows() { 454 final int connectionId; 455 synchronized (mLock) { 456 throwIfNotConnectedLocked(); 457 connectionId = mConnectionId; 458 } 459 // Calling out without a lock held. 460 return AccessibilityInteractionClient.getInstance() 461 .getWindows(connectionId); 462 } 463 464 /** 465 * Gets the root {@link AccessibilityNodeInfo} in the active window. 466 * 467 * @return The root info. 468 */ 469 public AccessibilityNodeInfo getRootInActiveWindow() { 470 final int connectionId; 471 synchronized (mLock) { 472 throwIfNotConnectedLocked(); 473 connectionId = mConnectionId; 474 } 475 // Calling out without a lock held. 476 return AccessibilityInteractionClient.getInstance() 477 .getRootInActiveWindow(connectionId); 478 } 479 480 /** 481 * A method for injecting an arbitrary input event. 482 * <p> 483 * <strong>Note:</strong> It is caller's responsibility to recycle the event. 484 * </p> 485 * @param event The event to inject. 486 * @param sync Whether to inject the event synchronously. 487 * @return Whether event injection succeeded. 488 */ 489 public boolean injectInputEvent(InputEvent event, boolean sync) { 490 synchronized (mLock) { 491 throwIfNotConnectedLocked(); 492 } 493 try { 494 if (DEBUG) { 495 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync); 496 } 497 // Calling out without a lock held. 498 return mUiAutomationConnection.injectInputEvent(event, sync); 499 } catch (RemoteException re) { 500 Log.e(LOG_TAG, "Error while injecting input event!", re); 501 } 502 return false; 503 } 504 505 /** 506 * Sets the device rotation. A client can freeze the rotation in 507 * desired state or freeze the rotation to its current state or 508 * unfreeze the rotation (rotating the device changes its rotation 509 * state). 510 * 511 * @param rotation The desired rotation. 512 * @return Whether the rotation was set successfully. 513 * 514 * @see #ROTATION_FREEZE_0 515 * @see #ROTATION_FREEZE_90 516 * @see #ROTATION_FREEZE_180 517 * @see #ROTATION_FREEZE_270 518 * @see #ROTATION_FREEZE_CURRENT 519 * @see #ROTATION_UNFREEZE 520 */ 521 public boolean setRotation(int rotation) { 522 synchronized (mLock) { 523 throwIfNotConnectedLocked(); 524 } 525 switch (rotation) { 526 case ROTATION_FREEZE_0: 527 case ROTATION_FREEZE_90: 528 case ROTATION_FREEZE_180: 529 case ROTATION_FREEZE_270: 530 case ROTATION_UNFREEZE: 531 case ROTATION_FREEZE_CURRENT: { 532 try { 533 // Calling out without a lock held. 534 mUiAutomationConnection.setRotation(rotation); 535 return true; 536 } catch (RemoteException re) { 537 Log.e(LOG_TAG, "Error while setting rotation!", re); 538 } 539 } return false; 540 default: { 541 throw new IllegalArgumentException("Invalid rotation."); 542 } 543 } 544 } 545 546 /** 547 * Executes a command and waits for a specific accessibility event up to a 548 * given wait timeout. To detect a sequence of events one can implement a 549 * filter that keeps track of seen events of the expected sequence and 550 * returns true after the last event of that sequence is received. 551 * <p> 552 * <strong>Note:</strong> It is caller's responsibility to recycle the returned event. 553 * </p> 554 * @param command The command to execute. 555 * @param filter Filter that recognizes the expected event. 556 * @param timeoutMillis The wait timeout in milliseconds. 557 * 558 * @throws TimeoutException If the expected event is not received within the timeout. 559 */ 560 public AccessibilityEvent executeAndWaitForEvent(Runnable command, 561 AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException { 562 // Acquire the lock and prepare for receiving events. 563 synchronized (mLock) { 564 throwIfNotConnectedLocked(); 565 mEventQueue.clear(); 566 // Prepare to wait for an event. 567 mWaitingForEventDelivery = true; 568 } 569 570 // Note: We have to release the lock since calling out with this lock held 571 // can bite. We will correctly filter out events from other interactions, 572 // so starting to collect events before running the action is just fine. 573 574 // We will ignore events from previous interactions. 575 final long executionStartTimeMillis = SystemClock.uptimeMillis(); 576 // Execute the command *without* the lock being held. 577 command.run(); 578 579 // Acquire the lock and wait for the event. 580 synchronized (mLock) { 581 try { 582 // Wait for the event. 583 final long startTimeMillis = SystemClock.uptimeMillis(); 584 while (true) { 585 // Drain the event queue 586 while (!mEventQueue.isEmpty()) { 587 AccessibilityEvent event = mEventQueue.remove(0); 588 // Ignore events from previous interactions. 589 if (event.getEventTime() < executionStartTimeMillis) { 590 continue; 591 } 592 if (filter.accept(event)) { 593 return event; 594 } 595 event.recycle(); 596 } 597 // Check if timed out and if not wait. 598 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 599 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 600 if (remainingTimeMillis <= 0) { 601 throw new TimeoutException("Expected event not received within: " 602 + timeoutMillis + " ms."); 603 } 604 try { 605 mLock.wait(remainingTimeMillis); 606 } catch (InterruptedException ie) { 607 /* ignore */ 608 } 609 } 610 } finally { 611 mWaitingForEventDelivery = false; 612 mEventQueue.clear(); 613 mLock.notifyAll(); 614 } 615 } 616 } 617 618 /** 619 * Waits for the accessibility event stream to become idle, which is not to 620 * have received an accessibility event within <code>idleTimeoutMillis</code>. 621 * The total time spent to wait for an idle accessibility event stream is bounded 622 * by the <code>globalTimeoutMillis</code>. 623 * 624 * @param idleTimeoutMillis The timeout in milliseconds between two events 625 * to consider the device idle. 626 * @param globalTimeoutMillis The maximal global timeout in milliseconds in 627 * which to wait for an idle state. 628 * 629 * @throws TimeoutException If no idle state was detected within 630 * <code>globalTimeoutMillis.</code> 631 */ 632 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis) 633 throws TimeoutException { 634 synchronized (mLock) { 635 throwIfNotConnectedLocked(); 636 637 final long startTimeMillis = SystemClock.uptimeMillis(); 638 if (mLastEventTimeMillis <= 0) { 639 mLastEventTimeMillis = startTimeMillis; 640 } 641 642 while (true) { 643 final long currentTimeMillis = SystemClock.uptimeMillis(); 644 // Did we get idle state within the global timeout? 645 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis; 646 final long remainingGlobalTimeMillis = 647 globalTimeoutMillis - elapsedGlobalTimeMillis; 648 if (remainingGlobalTimeMillis <= 0) { 649 throw new TimeoutException("No idle state with idle timeout: " 650 + idleTimeoutMillis + " within global timeout: " 651 + globalTimeoutMillis); 652 } 653 // Did we get an idle state within the idle timeout? 654 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis; 655 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis; 656 if (remainingIdleTimeMillis <= 0) { 657 return; 658 } 659 try { 660 mLock.wait(remainingIdleTimeMillis); 661 } catch (InterruptedException ie) { 662 /* ignore */ 663 } 664 } 665 } 666 } 667 668 /** 669 * Takes a screenshot. 670 * 671 * @return The screenshot bitmap on success, null otherwise. 672 */ 673 public Bitmap takeScreenshot() { 674 synchronized (mLock) { 675 throwIfNotConnectedLocked(); 676 } 677 Display display = DisplayManagerGlobal.getInstance() 678 .getRealDisplay(Display.DEFAULT_DISPLAY); 679 Point displaySize = new Point(); 680 display.getRealSize(displaySize); 681 final int displayWidth = displaySize.x; 682 final int displayHeight = displaySize.y; 683 684 final float screenshotWidth; 685 final float screenshotHeight; 686 687 final int rotation = display.getRotation(); 688 switch (rotation) { 689 case ROTATION_FREEZE_0: { 690 screenshotWidth = displayWidth; 691 screenshotHeight = displayHeight; 692 } break; 693 case ROTATION_FREEZE_90: { 694 screenshotWidth = displayHeight; 695 screenshotHeight = displayWidth; 696 } break; 697 case ROTATION_FREEZE_180: { 698 screenshotWidth = displayWidth; 699 screenshotHeight = displayHeight; 700 } break; 701 case ROTATION_FREEZE_270: { 702 screenshotWidth = displayHeight; 703 screenshotHeight = displayWidth; 704 } break; 705 default: { 706 throw new IllegalArgumentException("Invalid rotation: " 707 + rotation); 708 } 709 } 710 711 // Take the screenshot 712 Bitmap screenShot = null; 713 try { 714 // Calling out without a lock held. 715 screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth, 716 (int) screenshotHeight); 717 if (screenShot == null) { 718 return null; 719 } 720 } catch (RemoteException re) { 721 Log.e(LOG_TAG, "Error while taking screnshot!", re); 722 return null; 723 } 724 725 // Rotate the screenshot to the current orientation 726 if (rotation != ROTATION_FREEZE_0) { 727 Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight, 728 Bitmap.Config.ARGB_8888); 729 Canvas canvas = new Canvas(unrotatedScreenShot); 730 canvas.translate(unrotatedScreenShot.getWidth() / 2, 731 unrotatedScreenShot.getHeight() / 2); 732 canvas.rotate(getDegreesForRotation(rotation)); 733 canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2); 734 canvas.drawBitmap(screenShot, 0, 0, null); 735 canvas.setBitmap(null); 736 screenShot.recycle(); 737 screenShot = unrotatedScreenShot; 738 } 739 740 // Optimization 741 screenShot.setHasAlpha(false); 742 743 return screenShot; 744 } 745 746 /** 747 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether 748 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing 749 * potentially undesirable actions such as calling 911 or posting on public forums etc. 750 * 751 * @param enable whether to run in a "monkey" mode or not. Default is not. 752 * @see ActivityManager#isUserAMonkey() 753 */ 754 public void setRunAsMonkey(boolean enable) { 755 synchronized (mLock) { 756 throwIfNotConnectedLocked(); 757 } 758 try { 759 ActivityManagerNative.getDefault().setUserIsMonkey(enable); 760 } catch (RemoteException re) { 761 Log.e(LOG_TAG, "Error while setting run as monkey!", re); 762 } 763 } 764 765 /** 766 * Clears the frame statistics for the content of a given window. These 767 * statistics contain information about the most recently rendered content 768 * frames. 769 * 770 * @param windowId The window id. 771 * @return Whether the window is present and its frame statistics 772 * were cleared. 773 * 774 * @see android.view.WindowContentFrameStats 775 * @see #getWindowContentFrameStats(int) 776 * @see #getWindows() 777 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 778 */ 779 public boolean clearWindowContentFrameStats(int windowId) { 780 synchronized (mLock) { 781 throwIfNotConnectedLocked(); 782 } 783 try { 784 if (DEBUG) { 785 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId); 786 } 787 // Calling out without a lock held. 788 return mUiAutomationConnection.clearWindowContentFrameStats(windowId); 789 } catch (RemoteException re) { 790 Log.e(LOG_TAG, "Error clearing window content frame stats!", re); 791 } 792 return false; 793 } 794 795 /** 796 * Gets the frame statistics for a given window. These statistics contain 797 * information about the most recently rendered content frames. 798 * <p> 799 * A typical usage requires clearing the window frame statistics via {@link 800 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and 801 * finally getting the window frame statistics via calling this method. 802 * </p> 803 * <pre> 804 * // Assume we have at least one window. 805 * final int windowId = getWindows().get(0).getId(); 806 * 807 * // Start with a clean slate. 808 * uiAutimation.clearWindowContentFrameStats(windowId); 809 * 810 * // Do stuff with the UI. 811 * 812 * // Get the frame statistics. 813 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId); 814 * </pre> 815 * 816 * @param windowId The window id. 817 * @return The window frame statistics, or null if the window is not present. 818 * 819 * @see android.view.WindowContentFrameStats 820 * @see #clearWindowContentFrameStats(int) 821 * @see #getWindows() 822 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 823 */ 824 public WindowContentFrameStats getWindowContentFrameStats(int windowId) { 825 synchronized (mLock) { 826 throwIfNotConnectedLocked(); 827 } 828 try { 829 if (DEBUG) { 830 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId); 831 } 832 // Calling out without a lock held. 833 return mUiAutomationConnection.getWindowContentFrameStats(windowId); 834 } catch (RemoteException re) { 835 Log.e(LOG_TAG, "Error getting window content frame stats!", re); 836 } 837 return null; 838 } 839 840 /** 841 * Clears the window animation rendering statistics. These statistics contain 842 * information about the most recently rendered window animation frames, i.e. 843 * for window transition animations. 844 * 845 * @see android.view.WindowAnimationFrameStats 846 * @see #getWindowAnimationFrameStats() 847 * @see android.R.styleable#WindowAnimation 848 */ 849 public void clearWindowAnimationFrameStats() { 850 synchronized (mLock) { 851 throwIfNotConnectedLocked(); 852 } 853 try { 854 if (DEBUG) { 855 Log.i(LOG_TAG, "Clearing window animation frame stats"); 856 } 857 // Calling out without a lock held. 858 mUiAutomationConnection.clearWindowAnimationFrameStats(); 859 } catch (RemoteException re) { 860 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re); 861 } 862 } 863 864 /** 865 * Gets the window animation frame statistics. These statistics contain 866 * information about the most recently rendered window animation frames, i.e. 867 * for window transition animations. 868 * 869 * <p> 870 * A typical usage requires clearing the window animation frame statistics via 871 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes 872 * a window transition which uses a window animation and finally getting the window 873 * animation frame statistics by calling this method. 874 * </p> 875 * <pre> 876 * // Start with a clean slate. 877 * uiAutimation.clearWindowAnimationFrameStats(); 878 * 879 * // Do stuff to trigger a window transition. 880 * 881 * // Get the frame statistics. 882 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats(); 883 * </pre> 884 * 885 * @return The window animation frame statistics. 886 * 887 * @see android.view.WindowAnimationFrameStats 888 * @see #clearWindowAnimationFrameStats() 889 * @see android.R.styleable#WindowAnimation 890 */ 891 public WindowAnimationFrameStats getWindowAnimationFrameStats() { 892 synchronized (mLock) { 893 throwIfNotConnectedLocked(); 894 } 895 try { 896 if (DEBUG) { 897 Log.i(LOG_TAG, "Getting window animation frame stats"); 898 } 899 // Calling out without a lock held. 900 return mUiAutomationConnection.getWindowAnimationFrameStats(); 901 } catch (RemoteException re) { 902 Log.e(LOG_TAG, "Error getting window animation frame stats!", re); 903 } 904 return null; 905 } 906 907 /** 908 * Grants a runtime permission to a package for a user. 909 * @param packageName The package to which to grant. 910 * @param permission The permission to grant. 911 * @return Whether granting succeeded. 912 * 913 * @hide 914 */ 915 @TestApi 916 public boolean grantRuntimePermission(String packageName, String permission, 917 UserHandle userHandle) { 918 synchronized (mLock) { 919 throwIfNotConnectedLocked(); 920 } 921 try { 922 if (DEBUG) { 923 Log.i(LOG_TAG, "Granting runtime permission"); 924 } 925 // Calling out without a lock held. 926 mUiAutomationConnection.grantRuntimePermission(packageName, 927 permission, userHandle.getIdentifier()); 928 // TODO: The package manager API should return boolean. 929 return true; 930 } catch (RemoteException re) { 931 Log.e(LOG_TAG, "Error granting runtime permission", re); 932 } 933 return false; 934 } 935 936 /** 937 * Revokes a runtime permission from a package for a user. 938 * @param packageName The package from which to revoke. 939 * @param permission The permission to revoke. 940 * @return Whether revoking succeeded. 941 * 942 * @hide 943 */ 944 @TestApi 945 public boolean revokeRuntimePermission(String packageName, String permission, 946 UserHandle userHandle) { 947 synchronized (mLock) { 948 throwIfNotConnectedLocked(); 949 } 950 try { 951 if (DEBUG) { 952 Log.i(LOG_TAG, "Revoking runtime permission"); 953 } 954 // Calling out without a lock held. 955 mUiAutomationConnection.revokeRuntimePermission(packageName, 956 permission, userHandle.getIdentifier()); 957 // TODO: The package manager API should return boolean. 958 return true; 959 } catch (RemoteException re) { 960 Log.e(LOG_TAG, "Error revoking runtime permission", re); 961 } 962 return false; 963 } 964 965 /** 966 * Executes a shell command. This method returs a file descriptor that points 967 * to the standard output stream. The command execution is similar to running 968 * "adb shell <command>" from a host connected to the device. 969 * <p> 970 * <strong>Note:</strong> It is your responsibility to close the retunred file 971 * descriptor once you are done reading. 972 * </p> 973 * 974 * @param command The command to execute. 975 * @return A file descriptor to the standard output stream. 976 */ 977 public ParcelFileDescriptor executeShellCommand(String command) { 978 synchronized (mLock) { 979 throwIfNotConnectedLocked(); 980 } 981 982 ParcelFileDescriptor source = null; 983 ParcelFileDescriptor sink = null; 984 985 try { 986 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 987 source = pipe[0]; 988 sink = pipe[1]; 989 990 // Calling out without a lock held. 991 mUiAutomationConnection.executeShellCommand(command, sink); 992 } catch (IOException ioe) { 993 Log.e(LOG_TAG, "Error executing shell command!", ioe); 994 } catch (RemoteException re) { 995 Log.e(LOG_TAG, "Error executing shell command!", re); 996 } finally { 997 IoUtils.closeQuietly(sink); 998 } 999 1000 return source; 1001 } 1002 1003 private static float getDegreesForRotation(int value) { 1004 switch (value) { 1005 case Surface.ROTATION_90: { 1006 return 360f - 90f; 1007 } 1008 case Surface.ROTATION_180: { 1009 return 360f - 180f; 1010 } 1011 case Surface.ROTATION_270: { 1012 return 360f - 270f; 1013 } default: { 1014 return 0; 1015 } 1016 } 1017 } 1018 1019 private boolean isConnectedLocked() { 1020 return mConnectionId != CONNECTION_ID_UNDEFINED; 1021 } 1022 1023 private void throwIfConnectedLocked() { 1024 if (mConnectionId != CONNECTION_ID_UNDEFINED) { 1025 throw new IllegalStateException("UiAutomation not connected!"); 1026 } 1027 } 1028 1029 private void throwIfNotConnectedLocked() { 1030 if (!isConnectedLocked()) { 1031 throw new IllegalStateException("UiAutomation not connected!"); 1032 } 1033 } 1034 1035 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { 1036 1037 public IAccessibilityServiceClientImpl(Looper looper) { 1038 super(null, looper, new Callbacks() { 1039 @Override 1040 public void init(int connectionId, IBinder windowToken) { 1041 synchronized (mLock) { 1042 mConnectionId = connectionId; 1043 mLock.notifyAll(); 1044 } 1045 } 1046 1047 @Override 1048 public void onServiceConnected() { 1049 /* do nothing */ 1050 } 1051 1052 @Override 1053 public void onInterrupt() { 1054 /* do nothing */ 1055 } 1056 1057 @Override 1058 public boolean onGesture(int gestureId) { 1059 /* do nothing */ 1060 return false; 1061 } 1062 1063 @Override 1064 public void onAccessibilityEvent(AccessibilityEvent event) { 1065 synchronized (mLock) { 1066 mLastEventTimeMillis = event.getEventTime(); 1067 if (mWaitingForEventDelivery) { 1068 mEventQueue.add(AccessibilityEvent.obtain(event)); 1069 } 1070 mLock.notifyAll(); 1071 } 1072 // Calling out only without a lock held. 1073 final OnAccessibilityEventListener listener = mOnAccessibilityEventListener; 1074 if (listener != null) { 1075 listener.onAccessibilityEvent(AccessibilityEvent.obtain(event)); 1076 } 1077 } 1078 1079 @Override 1080 public boolean onKeyEvent(KeyEvent event) { 1081 return false; 1082 } 1083 1084 @Override 1085 public void onMagnificationChanged(@NonNull Region region, 1086 float scale, float centerX, float centerY) { 1087 /* do nothing */ 1088 } 1089 1090 @Override 1091 public void onSoftKeyboardShowModeChanged(int showMode) { 1092 /* do nothing */ 1093 } 1094 1095 @Override 1096 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) { 1097 /* do nothing */ 1098 } 1099 }); 1100 } 1101 } 1102} 1103