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 static android.view.Display.DEFAULT_DISPLAY; 20 21 import android.accessibilityservice.AccessibilityGestureEvent; 22 import android.accessibilityservice.AccessibilityService; 23 import android.accessibilityservice.AccessibilityService.Callbacks; 24 import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper; 25 import android.accessibilityservice.AccessibilityServiceInfo; 26 import android.accessibilityservice.IAccessibilityServiceClient; 27 import android.accessibilityservice.IAccessibilityServiceConnection; 28 import android.accessibilityservice.MagnificationConfig; 29 import android.annotation.IntDef; 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.annotation.SuppressLint; 33 import android.annotation.TestApi; 34 import android.compat.annotation.UnsupportedAppUsage; 35 import android.content.Context; 36 import android.graphics.Bitmap; 37 import android.graphics.Point; 38 import android.graphics.Rect; 39 import android.graphics.Region; 40 import android.hardware.HardwareBuffer; 41 import android.hardware.display.DisplayManagerGlobal; 42 import android.os.Build; 43 import android.os.Handler; 44 import android.os.HandlerThread; 45 import android.os.IBinder; 46 import android.os.Looper; 47 import android.os.ParcelFileDescriptor; 48 import android.os.Process; 49 import android.os.RemoteException; 50 import android.os.SystemClock; 51 import android.os.UserHandle; 52 import android.os.UserManager; 53 import android.util.ArraySet; 54 import android.util.DebugUtils; 55 import android.util.Log; 56 import android.util.SparseArray; 57 import android.view.Display; 58 import android.view.InputEvent; 59 import android.view.KeyEvent; 60 import android.view.MotionEvent; 61 import android.view.Surface; 62 import android.view.SurfaceControl; 63 import android.view.View; 64 import android.view.ViewRootImpl; 65 import android.view.Window; 66 import android.view.WindowAnimationFrameStats; 67 import android.view.WindowContentFrameStats; 68 import android.view.accessibility.AccessibilityCache; 69 import android.view.accessibility.AccessibilityEvent; 70 import android.view.accessibility.AccessibilityInteractionClient; 71 import android.view.accessibility.AccessibilityNodeInfo; 72 import android.view.accessibility.AccessibilityWindowInfo; 73 import android.view.accessibility.IAccessibilityInteractionConnection; 74 import android.view.inputmethod.EditorInfo; 75 import android.window.ScreenCapture; 76 import android.window.ScreenCapture.ScreenshotHardwareBuffer; 77 78 import com.android.internal.annotations.GuardedBy; 79 import com.android.internal.annotations.VisibleForTesting; 80 import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; 81 import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; 82 import com.android.internal.util.Preconditions; 83 import com.android.internal.util.function.pooled.PooledLambda; 84 85 import libcore.io.IoUtils; 86 87 import java.io.IOException; 88 import java.lang.annotation.Retention; 89 import java.lang.annotation.RetentionPolicy; 90 import java.util.ArrayList; 91 import java.util.List; 92 import java.util.Set; 93 import java.util.concurrent.TimeoutException; 94 95 /** 96 * Class for interacting with the device's UI by simulation user actions and 97 * introspection of the screen content. It relies on the platform accessibility 98 * APIs to introspect the screen and to perform some actions on the remote view 99 * tree. It also allows injecting of arbitrary raw input events simulating user 100 * interaction with keyboards and touch devices. One can think of a UiAutomation 101 * as a special type of {@link android.accessibilityservice.AccessibilityService} 102 * which does not provide hooks for the service life cycle and exposes other 103 * APIs that are useful for UI test automation. 104 * <p> 105 * The APIs exposed by this class are low-level to maximize flexibility when 106 * developing UI test automation tools and libraries. Generally, a UiAutomation 107 * client should be using a higher-level library or implement high-level functions. 108 * For example, performing a tap on the screen requires construction and injecting 109 * of a touch down and up events which have to be delivered to the system by a 110 * call to {@link #injectInputEvent(InputEvent, boolean)}. 111 * </p> 112 * <p> 113 * The APIs exposed by this class operate across applications enabling a client 114 * to write tests that cover use cases spanning over multiple applications. For 115 * example, going to the settings application to change a setting and then 116 * interacting with another application whose behavior depends on that setting. 117 * </p> 118 */ 119 public final class UiAutomation { 120 121 private static final String LOG_TAG = UiAutomation.class.getSimpleName(); 122 123 private static final boolean DEBUG = false; 124 private static final boolean VERBOSE = false; 125 126 private static final int CONNECTION_ID_UNDEFINED = -1; 127 128 private static final long CONNECT_TIMEOUT_MILLIS = 5000; 129 130 /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */ 131 public static final int ROTATION_UNFREEZE = -2; 132 133 /** Rotation constant: Freeze rotation to its current state. */ 134 public static final int ROTATION_FREEZE_CURRENT = -1; 135 136 /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */ 137 public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0; 138 139 /** Rotation constant: Freeze rotation to 90 degrees . */ 140 public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90; 141 142 /** Rotation constant: Freeze rotation to 180 degrees . */ 143 public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180; 144 145 /** Rotation constant: Freeze rotation to 270 degrees . */ 146 public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270; 147 148 @Retention(RetentionPolicy.SOURCE) 149 @IntDef(value = { 150 ConnectionState.DISCONNECTED, 151 ConnectionState.CONNECTING, 152 ConnectionState.CONNECTED, 153 ConnectionState.FAILED 154 }) 155 private @interface ConnectionState { 156 /** The initial state before {@link #connect} or after {@link #disconnect} is called. */ 157 int DISCONNECTED = 0; 158 /** 159 * The temporary state after {@link #connect} is called. Will transition to 160 * {@link #CONNECTED} or {@link #FAILED} depending on whether {@link #connect} succeeds or 161 * not. 162 */ 163 int CONNECTING = 1; 164 /** The state when {@link #connect} has succeeded. */ 165 int CONNECTED = 2; 166 /** The state when {@link #connect} has failed. */ 167 int FAILED = 3; 168 } 169 170 /** 171 * UiAutomation suppresses accessibility services by default. This flag specifies that 172 * existing accessibility services should continue to run, and that new ones may start. 173 * This flag is set when obtaining the UiAutomation from 174 * {@link Instrumentation#getUiAutomation(int)}. 175 */ 176 public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 0x00000001; 177 178 /** 179 * UiAutomation uses the accessibility subsystem by default. This flag provides an option to 180 * eliminate the overhead of engaging the accessibility subsystem for tests that do not need to 181 * interact with the user interface. Setting this flag disables methods that rely on 182 * accessibility. This flag is set when obtaining the UiAutomation from 183 * {@link Instrumentation#getUiAutomation(int)}. 184 */ 185 public static final int FLAG_DONT_USE_ACCESSIBILITY = 0x00000002; 186 187 /** 188 * UiAutomation sets {@link AccessibilityServiceInfo#isAccessibilityTool()} true by default. 189 * This flag provides the option to set this field false for tests exercising that property. 190 * 191 * @hide 192 */ 193 @TestApi 194 public static final int FLAG_NOT_ACCESSIBILITY_TOOL = 0x00000004; 195 196 /** 197 * Returned by {@link #getAdoptedShellPermissions} to indicate that all permissions have been 198 * adopted using {@link #adoptShellPermissionIdentity}. 199 * 200 * @hide 201 */ 202 @TestApi 203 @NonNull 204 public static final Set<String> ALL_PERMISSIONS = Set.of("_ALL_PERMISSIONS_"); 205 206 private final Object mLock = new Object(); 207 208 private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>(); 209 210 private final Handler mLocalCallbackHandler; 211 212 private final IUiAutomationConnection mUiAutomationConnection; 213 214 private final int mDisplayId; 215 216 private HandlerThread mRemoteCallbackThread; 217 218 private IAccessibilityServiceClient mClient; 219 220 private int mConnectionId = CONNECTION_ID_UNDEFINED; 221 222 private OnAccessibilityEventListener mOnAccessibilityEventListener; 223 224 private boolean mWaitingForEventDelivery; 225 226 private long mLastEventTimeMillis; 227 228 private @ConnectionState int mConnectionState = ConnectionState.DISCONNECTED; 229 230 private boolean mIsDestroyed; 231 232 private int mFlags; 233 234 private int mGenerationId = 0; 235 236 /** 237 * Listener for observing the {@link AccessibilityEvent} stream. 238 */ 239 public static interface OnAccessibilityEventListener { 240 241 /** 242 * Callback for receiving an {@link AccessibilityEvent}. 243 * <p> 244 * <strong>Note:</strong> This method is <strong>NOT</strong> executed 245 * on the main test thread. The client is responsible for proper 246 * synchronization. 247 * </p> 248 * <p> 249 * <strong>Note:</strong> It is responsibility of the client 250 * to recycle the received events to minimize object creation. 251 * </p> 252 * 253 * @param event The received event. 254 */ onAccessibilityEvent(AccessibilityEvent event)255 public void onAccessibilityEvent(AccessibilityEvent event); 256 } 257 258 /** 259 * Listener for filtering accessibility events. 260 */ 261 public static interface AccessibilityEventFilter { 262 263 /** 264 * Callback for determining whether an event is accepted or 265 * it is filtered out. 266 * 267 * @param event The event to process. 268 * @return True if the event is accepted, false to filter it out. 269 */ accept(AccessibilityEvent event)270 public boolean accept(AccessibilityEvent event); 271 } 272 273 /** 274 * Creates a new instance that will handle callbacks from the accessibility 275 * layer on the thread of the provided context main looper and perform requests for privileged 276 * operations on the provided connection, and filtering display-related features to the display 277 * associated with the context (or the user running the test, on devices that 278 * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}). 279 * 280 * @param context the context associated with the automation 281 * @param connection The connection for performing privileged operations. 282 * 283 * @hide 284 */ UiAutomation(Context context, IUiAutomationConnection connection)285 public UiAutomation(Context context, IUiAutomationConnection connection) { 286 this(getDisplayId(context), context.getMainLooper(), connection); 287 } 288 289 /** 290 * Creates a new instance that will handle callbacks from the accessibility 291 * layer on the thread of the provided looper and perform requests for privileged 292 * operations on the provided connection. 293 * 294 * @param looper The looper on which to execute accessibility callbacks. 295 * @param connection The connection for performing privileged operations. 296 * 297 * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead 298 * 299 * @hide 300 */ 301 @Deprecated 302 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) UiAutomation(Looper looper, IUiAutomationConnection connection)303 public UiAutomation(Looper looper, IUiAutomationConnection connection) { 304 this(DEFAULT_DISPLAY, looper, connection); 305 Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY"); 306 } 307 UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection)308 private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) { 309 Preconditions.checkArgument(looper != null, "Looper cannot be null!"); 310 Preconditions.checkArgument(connection != null, "Connection cannot be null!"); 311 312 mLocalCallbackHandler = new Handler(looper); 313 mUiAutomationConnection = connection; 314 mDisplayId = displayId; 315 316 Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier() 317 + " on display " + mDisplayId); 318 } 319 320 /** 321 * Connects this UiAutomation to the accessibility introspection APIs with default flags 322 * and default timeout. 323 * 324 * @hide 325 */ 326 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) connect()327 public void connect() { 328 try { 329 connectWithTimeout(0, CONNECT_TIMEOUT_MILLIS); 330 } catch (TimeoutException e) { 331 throw new RuntimeException(e); 332 } 333 } 334 335 /** 336 * Connects this UiAutomation to the accessibility introspection APIs with default timeout. 337 * 338 * @hide 339 */ connect(int flags)340 public void connect(int flags) { 341 try { 342 connectWithTimeout(flags, CONNECT_TIMEOUT_MILLIS); 343 } catch (TimeoutException e) { 344 throw new RuntimeException(e); 345 } 346 } 347 348 /** 349 * Connects this UiAutomation to the accessibility introspection APIs. 350 * 351 * @param flags Any flags to apply to the automation as it gets connected while 352 * {@link UiAutomation#FLAG_DONT_USE_ACCESSIBILITY} would keep the 353 * connection disconnected and not to register UiAutomation service. 354 * @param timeoutMillis The wait timeout in milliseconds 355 * 356 * @throws IllegalStateException If the connection to the accessibility subsystem is already 357 * established. 358 * @throws TimeoutException If not connected within the timeout 359 * @hide 360 */ connectWithTimeout(int flags, long timeoutMillis)361 public void connectWithTimeout(int flags, long timeoutMillis) throws TimeoutException { 362 if (DEBUG) { 363 Log.d(LOG_TAG, "connectWithTimeout: user=" + Process.myUserHandle().getIdentifier() 364 + ", flags=" + DebugUtils.flagsToString(UiAutomation.class, "FLAG_", flags) 365 + ", timeout=" + timeoutMillis + "ms"); 366 } 367 synchronized (mLock) { 368 throwIfConnectedLocked(); 369 if (mConnectionState == ConnectionState.CONNECTING) { 370 if (DEBUG) Log.d(LOG_TAG, "already connecting"); 371 return; 372 } 373 if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTING"); 374 mConnectionState = ConnectionState.CONNECTING; 375 mRemoteCallbackThread = new HandlerThread("UiAutomation"); 376 mRemoteCallbackThread.start(); 377 // Increment the generation since we are about to interact with a new client 378 mClient = new IAccessibilityServiceClientImpl( 379 mRemoteCallbackThread.getLooper(), ++mGenerationId); 380 } 381 382 try { 383 // Calling out without a lock held. 384 mUiAutomationConnection.connect(mClient, flags); 385 mFlags = flags; 386 // If UiAutomation is not allowed to use the accessibility subsystem, the 387 // connection state should keep disconnected and not to start the client connection. 388 if (!useAccessibility()) { 389 if (DEBUG) Log.d(LOG_TAG, "setting state to DISCONNECTED"); 390 mConnectionState = ConnectionState.DISCONNECTED; 391 return; 392 } 393 } catch (RemoteException re) { 394 throw new RuntimeException("Error while connecting " + this, re); 395 } 396 397 synchronized (mLock) { 398 final long startTimeMillis = SystemClock.uptimeMillis(); 399 while (true) { 400 if (mConnectionState == ConnectionState.CONNECTED) { 401 break; 402 } 403 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 404 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 405 if (remainingTimeMillis <= 0) { 406 if (DEBUG) Log.d(LOG_TAG, "setting state to FAILED"); 407 mConnectionState = ConnectionState.FAILED; 408 throw new TimeoutException("Timeout while connecting " + this); 409 } 410 try { 411 mLock.wait(remainingTimeMillis); 412 } catch (InterruptedException ie) { 413 /* ignore */ 414 } 415 } 416 } 417 } 418 419 /** 420 * Get the flags used to connect the service. 421 * 422 * @return The flags used to connect 423 * 424 * @hide 425 */ getFlags()426 public int getFlags() { 427 return mFlags; 428 } 429 430 /** 431 * Disconnects this UiAutomation from the accessibility introspection APIs. 432 * 433 * @hide 434 */ 435 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) disconnect()436 public void disconnect() { 437 synchronized (mLock) { 438 if (mConnectionState == ConnectionState.CONNECTING) { 439 throw new IllegalStateException( 440 "Cannot call disconnect() while connecting " + this); 441 } 442 if (useAccessibility() && mConnectionState == ConnectionState.DISCONNECTED) { 443 return; 444 } 445 mConnectionState = ConnectionState.DISCONNECTED; 446 mConnectionId = CONNECTION_ID_UNDEFINED; 447 // Increment the generation so we no longer interact with the existing client 448 ++mGenerationId; 449 } 450 try { 451 // Calling out without a lock held. 452 mUiAutomationConnection.disconnect(); 453 } catch (RemoteException re) { 454 throw new RuntimeException("Error while disconnecting " + this, re); 455 } finally { 456 if (mRemoteCallbackThread != null) { 457 mRemoteCallbackThread.quit(); 458 mRemoteCallbackThread = null; 459 } 460 } 461 } 462 463 /** 464 * The id of the {@link IAccessibilityInteractionConnection} for querying 465 * the screen content. This is here for legacy purposes since some tools use 466 * hidden APIs to introspect the screen. 467 * 468 * @hide 469 */ getConnectionId()470 public int getConnectionId() { 471 synchronized (mLock) { 472 throwIfNotConnectedLocked(); 473 return mConnectionId; 474 } 475 } 476 477 /** 478 * Reports if the object has been destroyed 479 * 480 * @return {code true} if the object has been destroyed. 481 * 482 * @hide 483 */ isDestroyed()484 public boolean isDestroyed() { 485 return mIsDestroyed; 486 } 487 488 /** 489 * Sets a callback for observing the stream of {@link AccessibilityEvent}s. 490 * The callbacks are delivered on the main application thread. 491 * 492 * @param listener The callback. 493 * 494 * @throws IllegalStateException If the connection to the accessibility subsystem is not 495 * established. 496 */ setOnAccessibilityEventListener(OnAccessibilityEventListener listener)497 public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) { 498 synchronized (mLock) { 499 throwIfNotConnectedLocked(); 500 mOnAccessibilityEventListener = listener; 501 } 502 } 503 504 /** 505 * Destroy this UiAutomation. After calling this method, attempting to use the object will 506 * result in errors. 507 * 508 * @hide 509 */ 510 @TestApi destroy()511 public void destroy() { 512 disconnect(); 513 mIsDestroyed = true; 514 } 515 516 /** 517 * Clears the accessibility cache. 518 * 519 * @return {@code true} if the cache was cleared 520 * @see AccessibilityService#clearCache() 521 */ clearCache()522 public boolean clearCache() { 523 final int connectionId; 524 synchronized (mLock) { 525 throwIfNotConnectedLocked(); 526 connectionId = mConnectionId; 527 } 528 final AccessibilityCache cache = AccessibilityInteractionClient.getCache(connectionId); 529 if (cache == null) { 530 return false; 531 } 532 cache.clear(); 533 return true; 534 } 535 536 /** 537 * Checks if {@code node} is in the accessibility cache. 538 * 539 * @param node the node to check. 540 * @return {@code true} if {@code node} is in the cache. 541 * @hide 542 * @see AccessibilityService#isNodeInCache(AccessibilityNodeInfo) 543 */ 544 @TestApi isNodeInCache(@onNull AccessibilityNodeInfo node)545 public boolean isNodeInCache(@NonNull AccessibilityNodeInfo node) { 546 final int connectionId; 547 synchronized (mLock) { 548 throwIfNotConnectedLocked(); 549 connectionId = mConnectionId; 550 } 551 final AccessibilityCache cache = AccessibilityInteractionClient.getCache(connectionId); 552 if (cache == null) { 553 return false; 554 } 555 return cache.isNodeInCache(node); 556 } 557 558 /** 559 * Adopt the permission identity of the shell UID for all permissions. This allows 560 * you to call APIs protected permissions which normal apps cannot hold but are 561 * granted to the shell UID. If you already adopted all shell permissions by calling 562 * this method or {@link #adoptShellPermissionIdentity(String...)} a subsequent call will 563 * replace any previous adoption. Note that your permission state becomes that of the shell UID 564 * and it is not a combination of your and the shell UID permissions. 565 * <p> 566 * <strong>Note:<strong/> Calling this method adopts all shell permissions and overrides 567 * any subset of adopted permissions via {@link #adoptShellPermissionIdentity(String...)}. 568 * 569 * @see #adoptShellPermissionIdentity(String...) 570 * @see #dropShellPermissionIdentity() 571 */ adoptShellPermissionIdentity()572 public void adoptShellPermissionIdentity() { 573 try { 574 // Calling out without a lock held. 575 mUiAutomationConnection.adoptShellPermissionIdentity(Process.myUid(), null); 576 } catch (RemoteException re) { 577 throw re.rethrowFromSystemServer(); 578 } 579 } 580 581 /** 582 * Adopt the permission identity of the shell UID only for the provided permissions. 583 * This allows you to call APIs protected permissions which normal apps cannot hold 584 * but are granted to the shell UID. If you already adopted shell permissions by calling 585 * this method, or {@link #adoptShellPermissionIdentity()} a subsequent call will replace any 586 * previous adoption. 587 * <p> 588 * <strong>Note:<strong/> This method behave differently from 589 * {@link #adoptShellPermissionIdentity()}. Only the listed permissions will use the shell 590 * identity and other permissions will still check against the original UID 591 * 592 * @param permissions The permissions to adopt or <code>null</code> to adopt all. 593 * 594 * @see #adoptShellPermissionIdentity() 595 * @see #dropShellPermissionIdentity() 596 */ adoptShellPermissionIdentity(@ullable String... permissions)597 public void adoptShellPermissionIdentity(@Nullable String... permissions) { 598 try { 599 // Calling out without a lock held. 600 mUiAutomationConnection.adoptShellPermissionIdentity(Process.myUid(), permissions); 601 } catch (RemoteException re) { 602 throw re.rethrowFromSystemServer(); 603 } 604 } 605 606 /** 607 * Drop the shell permission identity adopted by a previous call to 608 * {@link #adoptShellPermissionIdentity()}. If you did not adopt the shell permission 609 * identity this method would be a no-op. 610 * 611 * @see #adoptShellPermissionIdentity() 612 */ dropShellPermissionIdentity()613 public void dropShellPermissionIdentity() { 614 try { 615 // Calling out without a lock held. 616 mUiAutomationConnection.dropShellPermissionIdentity(); 617 } catch (RemoteException re) { 618 throw re.rethrowFromSystemServer(); 619 } 620 } 621 622 /** 623 * Returns a list of adopted shell permissions using {@link #adoptShellPermissionIdentity}, 624 * returns and empty set if no permissions are adopted and {@link #ALL_PERMISSIONS} if all 625 * permissions are adopted. 626 * 627 * @hide 628 */ 629 @TestApi 630 @NonNull getAdoptedShellPermissions()631 public Set<String> getAdoptedShellPermissions() { 632 try { 633 final List<String> permissions = mUiAutomationConnection.getAdoptedShellPermissions(); 634 return permissions == null ? ALL_PERMISSIONS : new ArraySet<>(permissions); 635 } catch (RemoteException re) { 636 throw re.rethrowFromSystemServer(); 637 } 638 } 639 640 /** 641 * Performs a global action. Such an action can be performed at any moment 642 * regardless of the current application or user location in that application. 643 * For example going back, going home, opening recents, etc. 644 * 645 * @param action The action to perform. 646 * @return Whether the action was successfully performed. 647 * 648 * @throws IllegalStateException If the connection to the accessibility subsystem is not 649 * established. 650 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK 651 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME 652 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS 653 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS 654 */ performGlobalAction(int action)655 public final boolean performGlobalAction(int action) { 656 final IAccessibilityServiceConnection connection; 657 synchronized (mLock) { 658 throwIfNotConnectedLocked(); 659 connection = AccessibilityInteractionClient.getInstance() 660 .getConnection(mConnectionId); 661 } 662 // Calling out without a lock held. 663 if (connection != null) { 664 try { 665 return connection.performGlobalAction(action); 666 } catch (RemoteException re) { 667 Log.w(LOG_TAG, "Error while calling performGlobalAction", re); 668 } 669 } 670 return false; 671 } 672 673 /** 674 * Find the view that has the specified focus type. The search is performed 675 * across all windows. 676 * <p> 677 * <strong>Note:</strong> In order to access the windows you have to opt-in 678 * to retrieve the interactive windows by setting the 679 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 680 * Otherwise, the search will be performed only in the active window. 681 * </p> 682 * 683 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or 684 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. 685 * @return The node info of the focused view or null. 686 * 687 * @throws IllegalStateException If the connection to the accessibility subsystem is not 688 * established. 689 * @see AccessibilityNodeInfo#FOCUS_INPUT 690 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY 691 */ findFocus(int focus)692 public AccessibilityNodeInfo findFocus(int focus) { 693 synchronized (mLock) { 694 throwIfNotConnectedLocked(); 695 } 696 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, 697 AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus); 698 } 699 700 /** 701 * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation. 702 * This method is useful if one wants to change some of the dynamically 703 * configurable properties at runtime. 704 * 705 * @return The accessibility service info. 706 * 707 * @throws IllegalStateException If the connection to the accessibility subsystem is not 708 * established. 709 * @see AccessibilityServiceInfo 710 */ getServiceInfo()711 public final AccessibilityServiceInfo getServiceInfo() { 712 final IAccessibilityServiceConnection connection; 713 synchronized (mLock) { 714 throwIfNotConnectedLocked(); 715 connection = AccessibilityInteractionClient.getInstance() 716 .getConnection(mConnectionId); 717 } 718 // Calling out without a lock held. 719 if (connection != null) { 720 try { 721 return connection.getServiceInfo(); 722 } catch (RemoteException re) { 723 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); 724 } 725 } 726 return null; 727 } 728 729 /** 730 * Sets the {@link AccessibilityServiceInfo} that describes how this 731 * UiAutomation will be handled by the platform accessibility layer. 732 * 733 * @param info The info. 734 * 735 * @throws IllegalStateException If the connection to the accessibility subsystem is not 736 * established. 737 * @see AccessibilityServiceInfo 738 */ setServiceInfo(AccessibilityServiceInfo info)739 public final void setServiceInfo(AccessibilityServiceInfo info) { 740 final IAccessibilityServiceConnection connection; 741 synchronized (mLock) { 742 throwIfNotConnectedLocked(); 743 AccessibilityInteractionClient.getInstance().clearCache(mConnectionId); 744 connection = AccessibilityInteractionClient.getInstance() 745 .getConnection(mConnectionId); 746 } 747 // Calling out without a lock held. 748 if (connection != null) { 749 try { 750 connection.setServiceInfo(info); 751 } catch (RemoteException re) { 752 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); 753 } 754 } 755 } 756 757 /** 758 * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the 759 * {@link android.view.Display#DEFAULT_DISPLAY default display). 760 * 761 * <p> 762 * This method returns only the windows that a sighted user can interact with, as opposed to 763 * all windows. 764 765 * <p> 766 * For example, if there is a modal dialog shown and the user cannot touch 767 * anything behind it, then only the modal window will be reported 768 * (assuming it is the top one). For convenience the returned windows 769 * are ordered in a descending layer order, which is the windows that 770 * are higher in the Z-order are reported first. 771 * <p> 772 * <strong>Note:</strong> In order to access the windows you have to opt-in 773 * to retrieve the interactive windows by setting the 774 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 775 * 776 * @return The windows if there are windows such, otherwise an empty list. 777 * @throws IllegalStateException If the connection to the accessibility subsystem is not 778 * established. 779 */ getWindows()780 public List<AccessibilityWindowInfo> getWindows() { 781 if (DEBUG) { 782 Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId); 783 } 784 final int connectionId; 785 synchronized (mLock) { 786 throwIfNotConnectedLocked(); 787 connectionId = mConnectionId; 788 } 789 // Calling out without a lock held. 790 return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId, 791 mDisplayId); 792 } 793 794 /** 795 * Gets the windows on the screen of all displays. This method returns only the windows 796 * that a sighted user can interact with, as opposed to all windows. 797 * For example, if there is a modal dialog shown and the user cannot touch 798 * anything behind it, then only the modal window will be reported 799 * (assuming it is the top one). For convenience the returned windows 800 * are ordered in a descending layer order, which is the windows that 801 * are higher in the Z-order are reported first. 802 * <p> 803 * <strong>Note:</strong> In order to access the windows you have to opt-in 804 * to retrieve the interactive windows by setting the 805 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. 806 * </p> 807 * 808 * @return The windows of all displays if there are windows and the service is can retrieve 809 * them, otherwise an empty list. The key of SparseArray is display ID. 810 * @throws IllegalStateException If the connection to the accessibility subsystem is not 811 * established. 812 */ 813 @NonNull getWindowsOnAllDisplays()814 public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() { 815 final int connectionId; 816 synchronized (mLock) { 817 throwIfNotConnectedLocked(); 818 connectionId = mConnectionId; 819 } 820 // Calling out without a lock held. 821 return AccessibilityInteractionClient.getInstance() 822 .getWindowsOnAllDisplays(connectionId); 823 } 824 825 /** 826 * Gets the root {@link AccessibilityNodeInfo} in the active window. 827 * 828 * @return The root info. 829 * @throws IllegalStateException If the connection to the accessibility subsystem is not 830 * established. 831 */ getRootInActiveWindow()832 public AccessibilityNodeInfo getRootInActiveWindow() { 833 final int connectionId; 834 synchronized (mLock) { 835 throwIfNotConnectedLocked(); 836 connectionId = mConnectionId; 837 } 838 // Calling out without a lock held. 839 return AccessibilityInteractionClient.getInstance() 840 .getRootInActiveWindow(connectionId, 841 AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID); 842 } 843 844 /** 845 * A method for injecting an arbitrary input event. 846 * 847 * This method waits for all window container animations and surface operations to complete. 848 * 849 * <p> 850 * <strong>Note:</strong> It is caller's responsibility to recycle the event. 851 * </p> 852 * 853 * @param event The event to inject. 854 * @param sync Whether to inject the event synchronously. 855 * @return Whether event injection succeeded. 856 */ injectInputEvent(InputEvent event, boolean sync)857 public boolean injectInputEvent(InputEvent event, boolean sync) { 858 return injectInputEvent(event, sync, true /* waitForAnimations */); 859 } 860 861 /** 862 * A method for injecting an arbitrary input event, optionally waiting for window animations to 863 * complete. 864 * <p> 865 * <strong>Note:</strong> It is caller's responsibility to recycle the event. 866 * </p> 867 * 868 * @param event The event to inject. 869 * @param sync Whether to inject the event synchronously. 870 * @param waitForAnimations Whether to wait for all window container animations and surface 871 * operations to complete. 872 * @return Whether event injection succeeded. 873 * 874 * @hide 875 */ 876 @TestApi injectInputEvent(@onNull InputEvent event, boolean sync, boolean waitForAnimations)877 public boolean injectInputEvent(@NonNull InputEvent event, boolean sync, 878 boolean waitForAnimations) { 879 try { 880 if (DEBUG) { 881 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync + " waitForAnimations: " 882 + waitForAnimations); 883 } 884 // Calling out without a lock held. 885 return mUiAutomationConnection.injectInputEvent(event, sync, waitForAnimations); 886 } catch (RemoteException re) { 887 Log.e(LOG_TAG, "Error while injecting input event!", re); 888 } 889 return false; 890 } 891 892 /** 893 * Injects an arbitrary {@link InputEvent} to the accessibility input filter, for use in testing 894 * the accessibility input filter. 895 * 896 * Events injected to the input subsystem using the standard {@link #injectInputEvent} method 897 * skip the accessibility input filter to avoid feedback loops. 898 * 899 * @hide 900 */ 901 @TestApi injectInputEventToInputFilter(@onNull InputEvent event)902 public void injectInputEventToInputFilter(@NonNull InputEvent event) { 903 try { 904 mUiAutomationConnection.injectInputEventToInputFilter(event); 905 } catch (RemoteException re) { 906 Log.e(LOG_TAG, "Error while injecting input event to input filter", re); 907 } 908 } 909 910 /** 911 * Sets the system settings values that control the scaling factor for animations. The scale 912 * controls the animation playback speed for animations that respect these settings. Animations 913 * that do not respect the settings values will not be affected by this function. A lower scale 914 * value results in a faster speed. A value of <code>0</code> disables animations entirely. When 915 * animations are disabled services receive window change events more quickly which can reduce 916 * the potential by confusion by reducing the time during which windows are in transition. 917 * 918 * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED 919 * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED 920 * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE 921 * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE 922 * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE 923 * @param scale The scaling factor for all animations. 924 */ setAnimationScale(float scale)925 public void setAnimationScale(float scale) { 926 final IAccessibilityServiceConnection connection = 927 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 928 if (connection != null) { 929 try { 930 connection.setAnimationScale(scale); 931 } catch (RemoteException re) { 932 throw new RuntimeException(re); 933 } 934 } 935 } 936 937 /** 938 * A request for WindowManagerService to wait until all animations have completed and input 939 * information has been sent from WindowManager to native InputManager. 940 * 941 * @hide 942 */ 943 @TestApi syncInputTransactions()944 public void syncInputTransactions() { 945 try { 946 // Calling out without a lock held. 947 mUiAutomationConnection.syncInputTransactions(true /* waitForAnimations */); 948 } catch (RemoteException re) { 949 Log.e(LOG_TAG, "Error while syncing input transactions!", re); 950 } 951 } 952 953 /** 954 * A request for WindowManagerService to wait until all input information has been sent from 955 * WindowManager to native InputManager and optionally wait for animations to complete. 956 * 957 * @param waitForAnimations Whether to wait for all window container animations and surface 958 * operations to complete. 959 * 960 * @hide 961 */ 962 @TestApi syncInputTransactions(boolean waitForAnimations)963 public void syncInputTransactions(boolean waitForAnimations) { 964 try { 965 // Calling out without a lock held. 966 mUiAutomationConnection.syncInputTransactions(waitForAnimations); 967 } catch (RemoteException re) { 968 Log.e(LOG_TAG, "Error while syncing input transactions!", re); 969 } 970 } 971 972 /** 973 * Sets the device rotation. A client can freeze the rotation in 974 * desired state or freeze the rotation to its current state or 975 * unfreeze the rotation (rotating the device changes its rotation 976 * state). 977 * 978 * @param rotation The desired rotation. 979 * @return Whether the rotation was set successfully. 980 * 981 * @see #ROTATION_FREEZE_0 982 * @see #ROTATION_FREEZE_90 983 * @see #ROTATION_FREEZE_180 984 * @see #ROTATION_FREEZE_270 985 * @see #ROTATION_FREEZE_CURRENT 986 * @see #ROTATION_UNFREEZE 987 */ setRotation(int rotation)988 public boolean setRotation(int rotation) { 989 switch (rotation) { 990 case ROTATION_FREEZE_0: 991 case ROTATION_FREEZE_90: 992 case ROTATION_FREEZE_180: 993 case ROTATION_FREEZE_270: 994 case ROTATION_UNFREEZE: 995 case ROTATION_FREEZE_CURRENT: { 996 try { 997 // Calling out without a lock held. 998 mUiAutomationConnection.setRotation(rotation); 999 return true; 1000 } catch (RemoteException re) { 1001 Log.e(LOG_TAG, "Error while setting rotation!", re); 1002 } 1003 } return false; 1004 default: { 1005 throw new IllegalArgumentException("Invalid rotation."); 1006 } 1007 } 1008 } 1009 1010 /** 1011 * Executes a command and waits for a specific accessibility event up to a 1012 * given wait timeout. To detect a sequence of events one can implement a 1013 * filter that keeps track of seen events of the expected sequence and 1014 * returns true after the last event of that sequence is received. 1015 * <p> 1016 * <strong>Note:</strong> It is caller's responsibility to recycle the returned event. 1017 * </p> 1018 * 1019 * @param command The command to execute. 1020 * @param filter Filter that recognizes the expected event. 1021 * @param timeoutMillis The wait timeout in milliseconds. 1022 * 1023 * @throws TimeoutException If the expected event is not received within the timeout. 1024 * @throws IllegalStateException If the connection to the accessibility subsystem is not 1025 * established. 1026 */ executeAndWaitForEvent(Runnable command, AccessibilityEventFilter filter, long timeoutMillis)1027 public AccessibilityEvent executeAndWaitForEvent(Runnable command, 1028 AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException { 1029 // Acquire the lock and prepare for receiving events. 1030 synchronized (mLock) { 1031 throwIfNotConnectedLocked(); 1032 mEventQueue.clear(); 1033 // Prepare to wait for an event. 1034 mWaitingForEventDelivery = true; 1035 } 1036 1037 // Note: We have to release the lock since calling out with this lock held 1038 // can bite. We will correctly filter out events from other interactions, 1039 // so starting to collect events before running the action is just fine. 1040 1041 // We will ignore events from previous interactions. 1042 final long executionStartTimeMillis = SystemClock.uptimeMillis(); 1043 // Execute the command *without* the lock being held. 1044 command.run(); 1045 1046 List<AccessibilityEvent> receivedEvents = new ArrayList<>(); 1047 1048 // Acquire the lock and wait for the event. 1049 try { 1050 // Wait for the event. 1051 final long startTimeMillis = SystemClock.uptimeMillis(); 1052 while (true) { 1053 List<AccessibilityEvent> localEvents = new ArrayList<>(); 1054 synchronized (mLock) { 1055 localEvents.addAll(mEventQueue); 1056 mEventQueue.clear(); 1057 } 1058 // Drain the event queue 1059 while (!localEvents.isEmpty()) { 1060 AccessibilityEvent event = localEvents.remove(0); 1061 // Ignore events from previous interactions. 1062 if (event.getEventTime() < executionStartTimeMillis) { 1063 continue; 1064 } 1065 if (filter.accept(event)) { 1066 return event; 1067 } 1068 receivedEvents.add(event); 1069 } 1070 // Check if timed out and if not wait. 1071 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 1072 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 1073 if (remainingTimeMillis <= 0) { 1074 throw new TimeoutException("Expected event not received within: " 1075 + timeoutMillis + " ms among: " + receivedEvents); 1076 } 1077 synchronized (mLock) { 1078 if (mEventQueue.isEmpty()) { 1079 try { 1080 mLock.wait(remainingTimeMillis); 1081 } catch (InterruptedException ie) { 1082 /* ignore */ 1083 } 1084 } 1085 } 1086 } 1087 } finally { 1088 int size = receivedEvents.size(); 1089 for (int i = 0; i < size; i++) { 1090 receivedEvents.get(i).recycle(); 1091 } 1092 1093 synchronized (mLock) { 1094 mWaitingForEventDelivery = false; 1095 mEventQueue.clear(); 1096 mLock.notifyAll(); 1097 } 1098 } 1099 } 1100 1101 /** 1102 * Waits for the accessibility event stream to become idle, which is not to 1103 * have received an accessibility event within <code>idleTimeoutMillis</code>. 1104 * The total time spent to wait for an idle accessibility event stream is bounded 1105 * by the <code>globalTimeoutMillis</code>. 1106 * 1107 * @param idleTimeoutMillis The timeout in milliseconds between two events 1108 * to consider the device idle. 1109 * @param globalTimeoutMillis The maximal global timeout in milliseconds in 1110 * which to wait for an idle state. 1111 * 1112 * @throws TimeoutException If no idle state was detected within 1113 * <code>globalTimeoutMillis.</code> 1114 * @throws IllegalStateException If the connection to the accessibility subsystem is not 1115 * established. 1116 */ waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)1117 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis) 1118 throws TimeoutException { 1119 synchronized (mLock) { 1120 throwIfNotConnectedLocked(); 1121 1122 final long startTimeMillis = SystemClock.uptimeMillis(); 1123 if (mLastEventTimeMillis <= 0) { 1124 mLastEventTimeMillis = startTimeMillis; 1125 } 1126 1127 while (true) { 1128 final long currentTimeMillis = SystemClock.uptimeMillis(); 1129 // Did we get idle state within the global timeout? 1130 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis; 1131 final long remainingGlobalTimeMillis = 1132 globalTimeoutMillis - elapsedGlobalTimeMillis; 1133 if (remainingGlobalTimeMillis <= 0) { 1134 throw new TimeoutException("No idle state with idle timeout: " 1135 + idleTimeoutMillis + " within global timeout: " 1136 + globalTimeoutMillis); 1137 } 1138 // Did we get an idle state within the idle timeout? 1139 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis; 1140 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis; 1141 if (remainingIdleTimeMillis <= 0) { 1142 return; 1143 } 1144 try { 1145 mLock.wait(remainingIdleTimeMillis); 1146 } catch (InterruptedException ie) { 1147 /* ignore */ 1148 } 1149 } 1150 } 1151 } 1152 1153 /** 1154 * Takes a screenshot. 1155 * 1156 * @return The screenshot bitmap on success, null otherwise. 1157 */ takeScreenshot()1158 public Bitmap takeScreenshot() { 1159 if (DEBUG) { 1160 Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId); 1161 } 1162 Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId); 1163 Point displaySize = new Point(); 1164 display.getRealSize(displaySize); 1165 1166 // Take the screenshot 1167 ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = 1168 ScreenCapture.createSyncCaptureListener(); 1169 try { 1170 if (!mUiAutomationConnection.takeScreenshot( 1171 new Rect(0, 0, displaySize.x, displaySize.y), syncScreenCapture)) { 1172 return null; 1173 } 1174 } catch (RemoteException re) { 1175 Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re); 1176 return null; 1177 } 1178 1179 final ScreenshotHardwareBuffer screenshotBuffer = 1180 syncScreenCapture.getBuffer(); 1181 Bitmap screenShot = screenshotBuffer.asBitmap(); 1182 if (screenShot == null) { 1183 Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display " 1184 + mDisplayId); 1185 return null; 1186 } 1187 Bitmap swBitmap; 1188 try (HardwareBuffer buffer = screenshotBuffer.getHardwareBuffer()) { 1189 swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false); 1190 } 1191 screenShot.recycle(); 1192 1193 // Optimization 1194 swBitmap.setHasAlpha(false); 1195 return swBitmap; 1196 } 1197 1198 /** 1199 * Used to capture a screenshot of a Window. This can return null in the following cases: 1200 * 1. Window content hasn't been layed out. 1201 * 2. Window doesn't have a valid SurfaceControl 1202 * 3. An error occurred in SurfaceFlinger when trying to take the screenshot. 1203 * 1204 * @param window Window to take a screenshot of 1205 * 1206 * @return The screenshot bitmap on success, null otherwise. 1207 */ 1208 @Nullable takeScreenshot(@onNull Window window)1209 public Bitmap takeScreenshot(@NonNull Window window) { 1210 if (window == null) { 1211 return null; 1212 } 1213 1214 View decorView = window.peekDecorView(); 1215 if (decorView == null) { 1216 return null; 1217 } 1218 1219 ViewRootImpl viewRoot = decorView.getViewRootImpl(); 1220 if (viewRoot == null) { 1221 return null; 1222 } 1223 1224 SurfaceControl sc = viewRoot.getSurfaceControl(); 1225 if (!sc.isValid()) { 1226 return null; 1227 } 1228 1229 // Apply a sync transaction to ensure SurfaceFlinger is flushed before capturing a 1230 // screenshot. 1231 new SurfaceControl.Transaction().apply(true); 1232 ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = 1233 ScreenCapture.createSyncCaptureListener(); 1234 try { 1235 if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) { 1236 return null; 1237 } 1238 1239 } catch (RemoteException re) { 1240 Log.e(LOG_TAG, "Error while taking screenshot!", re); 1241 return null; 1242 } 1243 ScreenCapture.ScreenshotHardwareBuffer captureBuffer = 1244 syncScreenCapture.getBuffer(); 1245 Bitmap screenShot = captureBuffer.asBitmap(); 1246 Bitmap swBitmap; 1247 try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) { 1248 swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false); 1249 } 1250 1251 screenShot.recycle(); 1252 return swBitmap; 1253 } 1254 1255 /** 1256 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether 1257 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing 1258 * potentially undesirable actions such as calling 911 or posting on public forums etc. 1259 * 1260 * @param enable whether to run in a "monkey" mode or not. Default is not. 1261 * @see ActivityManager#isUserAMonkey() 1262 */ setRunAsMonkey(boolean enable)1263 public void setRunAsMonkey(boolean enable) { 1264 try { 1265 ActivityManager.getService().setUserIsMonkey(enable); 1266 } catch (RemoteException re) { 1267 Log.e(LOG_TAG, "Error while setting run as monkey!", re); 1268 } 1269 } 1270 1271 /** 1272 * Clears the frame statistics for the content of a given window. These 1273 * statistics contain information about the most recently rendered content 1274 * frames. 1275 * 1276 * @param windowId The window id. 1277 * @return Whether the window is present and its frame statistics 1278 * were cleared. 1279 * 1280 * @throws IllegalStateException If the connection to the accessibility subsystem is not 1281 * established. 1282 * @see android.view.WindowContentFrameStats 1283 * @see #getWindowContentFrameStats(int) 1284 * @see #getWindows() 1285 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 1286 */ clearWindowContentFrameStats(int windowId)1287 public boolean clearWindowContentFrameStats(int windowId) { 1288 synchronized (mLock) { 1289 throwIfNotConnectedLocked(); 1290 } 1291 try { 1292 if (DEBUG) { 1293 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId); 1294 } 1295 // Calling out without a lock held. 1296 return mUiAutomationConnection.clearWindowContentFrameStats(windowId); 1297 } catch (RemoteException re) { 1298 Log.e(LOG_TAG, "Error clearing window content frame stats!", re); 1299 } 1300 return false; 1301 } 1302 1303 /** 1304 * Gets the frame statistics for a given window. These statistics contain 1305 * information about the most recently rendered content frames. 1306 * <p> 1307 * A typical usage requires clearing the window frame statistics via {@link 1308 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and 1309 * finally getting the window frame statistics via calling this method. 1310 * </p> 1311 * <pre> 1312 * // Assume we have at least one window. 1313 * final int windowId = getWindows().get(0).getId(); 1314 * 1315 * // Start with a clean slate. 1316 * uiAutimation.clearWindowContentFrameStats(windowId); 1317 * 1318 * // Do stuff with the UI. 1319 * 1320 * // Get the frame statistics. 1321 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId); 1322 * </pre> 1323 * 1324 * @param windowId The window id. 1325 * @return The window frame statistics, or null if the window is not present. 1326 * 1327 * @throws IllegalStateException If the connection to the accessibility subsystem is not 1328 * established. 1329 * @see android.view.WindowContentFrameStats 1330 * @see #clearWindowContentFrameStats(int) 1331 * @see #getWindows() 1332 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId() 1333 */ getWindowContentFrameStats(int windowId)1334 public WindowContentFrameStats getWindowContentFrameStats(int windowId) { 1335 synchronized (mLock) { 1336 throwIfNotConnectedLocked(); 1337 } 1338 try { 1339 if (DEBUG) { 1340 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId); 1341 } 1342 // Calling out without a lock held. 1343 return mUiAutomationConnection.getWindowContentFrameStats(windowId); 1344 } catch (RemoteException re) { 1345 Log.e(LOG_TAG, "Error getting window content frame stats!", re); 1346 } 1347 return null; 1348 } 1349 1350 /** 1351 * Clears the window animation rendering statistics. These statistics contain 1352 * information about the most recently rendered window animation frames, i.e. 1353 * for window transition animations. 1354 * 1355 * @see android.view.WindowAnimationFrameStats 1356 * @see #getWindowAnimationFrameStats() 1357 * @see android.R.styleable#WindowAnimation 1358 * @deprecated animation-frames are no-longer used. Use Shared 1359 * <a href="https://perfetto.dev/docs/data-sources/frametimeline">FrameTimeline</a> 1360 * jank metrics instead. 1361 */ 1362 @Deprecated clearWindowAnimationFrameStats()1363 public void clearWindowAnimationFrameStats() { 1364 try { 1365 if (DEBUG) { 1366 Log.i(LOG_TAG, "Clearing window animation frame stats"); 1367 } 1368 // Calling out without a lock held. 1369 mUiAutomationConnection.clearWindowAnimationFrameStats(); 1370 } catch (RemoteException re) { 1371 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re); 1372 } 1373 } 1374 1375 /** 1376 * Gets the window animation frame statistics. These statistics contain 1377 * information about the most recently rendered window animation frames, i.e. 1378 * for window transition animations. 1379 * 1380 * <p> 1381 * A typical usage requires clearing the window animation frame statistics via 1382 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes 1383 * a window transition which uses a window animation and finally getting the window 1384 * animation frame statistics by calling this method. 1385 * </p> 1386 * <pre> 1387 * // Start with a clean slate. 1388 * uiAutimation.clearWindowAnimationFrameStats(); 1389 * 1390 * // Do stuff to trigger a window transition. 1391 * 1392 * // Get the frame statistics. 1393 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats(); 1394 * </pre> 1395 * 1396 * @return The window animation frame statistics. 1397 * 1398 * @see android.view.WindowAnimationFrameStats 1399 * @see #clearWindowAnimationFrameStats() 1400 * @see android.R.styleable#WindowAnimation 1401 * @deprecated animation-frames are no-longer used. 1402 */ 1403 @Deprecated getWindowAnimationFrameStats()1404 public WindowAnimationFrameStats getWindowAnimationFrameStats() { 1405 try { 1406 if (DEBUG) { 1407 Log.i(LOG_TAG, "Getting window animation frame stats"); 1408 } 1409 // Calling out without a lock held. 1410 return mUiAutomationConnection.getWindowAnimationFrameStats(); 1411 } catch (RemoteException re) { 1412 Log.e(LOG_TAG, "Error getting window animation frame stats!", re); 1413 } 1414 return null; 1415 } 1416 1417 /** 1418 * Grants a runtime permission to a package. 1419 * 1420 * @param packageName The package to which to grant. 1421 * @param permission The permission to grant. 1422 * @throws SecurityException if unable to grant the permission. 1423 */ grantRuntimePermission(String packageName, String permission)1424 public void grantRuntimePermission(String packageName, String permission) { 1425 grantRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle()); 1426 } 1427 1428 /** 1429 * @deprecated replaced by 1430 * {@link #grantRuntimePermissionAsUser(String, String, UserHandle)}. 1431 * @hide 1432 */ 1433 @Deprecated 1434 @TestApi grantRuntimePermission(String packageName, String permission, UserHandle userHandle)1435 public boolean grantRuntimePermission(String packageName, String permission, 1436 UserHandle userHandle) { 1437 grantRuntimePermissionAsUser(packageName, permission, userHandle); 1438 return true; 1439 } 1440 1441 /** 1442 * Grants a runtime permission to a package for a user. 1443 * 1444 * @param packageName The package to which to grant. 1445 * @param permission The permission to grant. 1446 * @throws SecurityException if unable to grant the permission. 1447 */ grantRuntimePermissionAsUser(String packageName, String permission, UserHandle userHandle)1448 public void grantRuntimePermissionAsUser(String packageName, String permission, 1449 UserHandle userHandle) { 1450 try { 1451 if (DEBUG) { 1452 Log.i(LOG_TAG, "Granting runtime permission (" + permission + ") to package " 1453 + packageName + " on user " + userHandle); 1454 } 1455 // Calling out without a lock held. 1456 mUiAutomationConnection.grantRuntimePermission(packageName, 1457 permission, userHandle.getIdentifier()); 1458 } catch (Exception e) { 1459 throw new SecurityException("Error granting runtime permission", e); 1460 } 1461 } 1462 1463 /** 1464 * Revokes a runtime permission from a package. 1465 * 1466 * @param packageName The package to which to grant. 1467 * @param permission The permission to grant. 1468 * @throws SecurityException if unable to revoke the permission. 1469 */ revokeRuntimePermission(String packageName, String permission)1470 public void revokeRuntimePermission(String packageName, String permission) { 1471 revokeRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle()); 1472 } 1473 1474 /** 1475 * @deprecated replaced by 1476 * {@link #revokeRuntimePermissionAsUser(String, String, UserHandle)}. 1477 * @hide 1478 */ 1479 @Deprecated 1480 @TestApi revokeRuntimePermission(String packageName, String permission, UserHandle userHandle)1481 public boolean revokeRuntimePermission(String packageName, String permission, 1482 UserHandle userHandle) { 1483 revokeRuntimePermissionAsUser(packageName, permission, userHandle); 1484 return true; 1485 } 1486 1487 /** 1488 * Revokes a runtime permission from a package. 1489 * 1490 * @param packageName The package to which to grant. 1491 * @param permission The permission to grant. 1492 * @throws SecurityException if unable to revoke the permission. 1493 */ revokeRuntimePermissionAsUser(String packageName, String permission, UserHandle userHandle)1494 public void revokeRuntimePermissionAsUser(String packageName, String permission, 1495 UserHandle userHandle) { 1496 try { 1497 if (DEBUG) { 1498 Log.i(LOG_TAG, "Revoking runtime permission"); 1499 } 1500 // Calling out without a lock held. 1501 mUiAutomationConnection.revokeRuntimePermission(packageName, 1502 permission, userHandle.getIdentifier()); 1503 } catch (Exception e) { 1504 throw new SecurityException("Error granting runtime permission", e); 1505 } 1506 } 1507 1508 /** 1509 * Executes a shell command. This method returns a file descriptor that points 1510 * to the standard output stream. The command execution is similar to running 1511 * "adb shell <command>" from a host connected to the device. 1512 * <p> 1513 * <strong>Note:</strong> It is your responsibility to close the returned file 1514 * descriptor once you are done reading. 1515 * </p> 1516 * 1517 * @param command The command to execute. 1518 * @return A file descriptor to the standard output stream. 1519 * 1520 * @see #adoptShellPermissionIdentity() 1521 */ executeShellCommand(String command)1522 public ParcelFileDescriptor executeShellCommand(String command) { 1523 warnIfBetterCommand(command); 1524 1525 ParcelFileDescriptor source = null; 1526 ParcelFileDescriptor sink = null; 1527 1528 try { 1529 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1530 source = pipe[0]; 1531 sink = pipe[1]; 1532 1533 // Calling out without a lock held. 1534 mUiAutomationConnection.executeShellCommand(command, sink, null); 1535 } catch (IOException ioe) { 1536 Log.e(LOG_TAG, "Error executing shell command!", ioe); 1537 } catch (RemoteException re) { 1538 Log.e(LOG_TAG, "Error executing shell command!", re); 1539 } finally { 1540 IoUtils.closeQuietly(sink); 1541 } 1542 1543 return source; 1544 } 1545 1546 /** 1547 * Executes a shell command. This method returns two file descriptors, 1548 * one that points to the standard output stream (element at index 0), and one that points 1549 * to the standard input stream (element at index 1). The command execution is similar 1550 * to running "adb shell <command>" from a host connected to the device. 1551 * <p> 1552 * <strong>Note:</strong> It is your responsibility to close the returned file 1553 * descriptors once you are done reading/writing. 1554 * </p> 1555 * 1556 * @param command The command to execute. 1557 * @return File descriptors (out, in) to the standard output/input streams. 1558 */ 1559 @SuppressLint("ArrayReturn") // For consistency with other APIs executeShellCommandRw(@onNull String command)1560 public @NonNull ParcelFileDescriptor[] executeShellCommandRw(@NonNull String command) { 1561 return executeShellCommandInternal(command, false /* includeStderr */); 1562 } 1563 1564 /** 1565 * Executes a shell command. This method returns three file descriptors, 1566 * one that points to the standard output stream (element at index 0), one that points 1567 * to the standard input stream (element at index 1), and one points to 1568 * standard error stream (element at index 2). The command execution is similar 1569 * to running "adb shell <command>" from a host connected to the device. 1570 * <p> 1571 * <strong>Note:</strong> It is your responsibility to close the returned file 1572 * descriptors once you are done reading/writing. 1573 * </p> 1574 * 1575 * @param command The command to execute. 1576 * @return File descriptors (out, in, err) to the standard output/input/error streams. 1577 */ 1578 @SuppressLint("ArrayReturn") // For consistency with other APIs executeShellCommandRwe(@onNull String command)1579 public @NonNull ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String command) { 1580 return executeShellCommandInternal(command, true /* includeStderr */); 1581 } 1582 1583 /** 1584 * @hide 1585 */ 1586 @VisibleForTesting getDisplayId()1587 public int getDisplayId() { 1588 return mDisplayId; 1589 } 1590 executeShellCommandInternal( String command, boolean includeStderr)1591 private ParcelFileDescriptor[] executeShellCommandInternal( 1592 String command, boolean includeStderr) { 1593 warnIfBetterCommand(command); 1594 1595 ParcelFileDescriptor source_read = null; 1596 ParcelFileDescriptor sink_read = null; 1597 1598 ParcelFileDescriptor source_write = null; 1599 ParcelFileDescriptor sink_write = null; 1600 1601 ParcelFileDescriptor stderr_source_read = null; 1602 ParcelFileDescriptor stderr_sink_read = null; 1603 1604 try { 1605 ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe(); 1606 source_read = pipe_read[0]; 1607 sink_read = pipe_read[1]; 1608 1609 ParcelFileDescriptor[] pipe_write = ParcelFileDescriptor.createPipe(); 1610 source_write = pipe_write[0]; 1611 sink_write = pipe_write[1]; 1612 1613 if (includeStderr) { 1614 ParcelFileDescriptor[] stderr_read = ParcelFileDescriptor.createPipe(); 1615 stderr_source_read = stderr_read[0]; 1616 stderr_sink_read = stderr_read[1]; 1617 } 1618 1619 // Calling out without a lock held. 1620 mUiAutomationConnection.executeShellCommandWithStderr( 1621 command, sink_read, source_write, stderr_sink_read); 1622 } catch (IOException ioe) { 1623 Log.e(LOG_TAG, "Error executing shell command!", ioe); 1624 } catch (RemoteException re) { 1625 Log.e(LOG_TAG, "Error executing shell command!", re); 1626 } finally { 1627 IoUtils.closeQuietly(sink_read); 1628 IoUtils.closeQuietly(source_write); 1629 IoUtils.closeQuietly(stderr_sink_read); 1630 } 1631 1632 ParcelFileDescriptor[] result = new ParcelFileDescriptor[includeStderr ? 3 : 2]; 1633 result[0] = source_read; 1634 result[1] = sink_write; 1635 if (includeStderr) { 1636 result[2] = stderr_source_read; 1637 } 1638 return result; 1639 } 1640 1641 @Override toString()1642 public String toString() { 1643 final StringBuilder stringBuilder = new StringBuilder(); 1644 stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode())); 1645 stringBuilder.append("[id=").append(mConnectionId); 1646 stringBuilder.append(", displayId=").append(mDisplayId); 1647 stringBuilder.append(", flags=").append(mFlags); 1648 stringBuilder.append("]"); 1649 return stringBuilder.toString(); 1650 } 1651 1652 @GuardedBy("mLock") throwIfConnectedLocked()1653 private void throwIfConnectedLocked() { 1654 if (mConnectionState == ConnectionState.CONNECTED) { 1655 throw new IllegalStateException("UiAutomation connected, " + this); 1656 } 1657 } 1658 1659 @GuardedBy("mLock") throwIfNotConnectedLocked()1660 private void throwIfNotConnectedLocked() { 1661 if (mConnectionState != ConnectionState.CONNECTED) { 1662 final String msg = useAccessibility() 1663 ? "UiAutomation not connected, " 1664 : "UiAutomation not connected: Accessibility-dependent method called with " 1665 + "FLAG_DONT_USE_ACCESSIBILITY set, "; 1666 throw new IllegalStateException(msg + this); 1667 } 1668 } 1669 warnIfBetterCommand(String cmd)1670 private void warnIfBetterCommand(String cmd) { 1671 if (cmd.startsWith("pm grant ")) { 1672 Log.w(LOG_TAG, "UiAutomation.grantRuntimePermission() " 1673 + "is more robust and should be used instead of 'pm grant'"); 1674 } else if (cmd.startsWith("pm revoke ")) { 1675 Log.w(LOG_TAG, "UiAutomation.revokeRuntimePermission() " 1676 + "is more robust and should be used instead of 'pm revoke'"); 1677 } 1678 } 1679 useAccessibility()1680 private boolean useAccessibility() { 1681 return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0; 1682 } 1683 1684 /** 1685 * Gets the display id associated with the UiAutomation context. 1686 * 1687 * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call 1688 * another one. 1689 */ getDisplayId(Context context)1690 private static int getDisplayId(Context context) { 1691 Preconditions.checkArgument(context != null, "Context cannot be null!"); 1692 1693 UserManager userManager = context.getSystemService(UserManager.class); 1694 // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple 1695 // users, the display is only set on devices that support that 1696 if (!userManager.isVisibleBackgroundUsersSupported()) { 1697 return DEFAULT_DISPLAY; 1698 } 1699 1700 int displayId = context.getDisplayId(); 1701 if (displayId == Display.INVALID_DISPLAY) { 1702 // Shouldn't happen, but we better handle it 1703 Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's" 1704 + " running in the display assigned to the user"); 1705 return getMainDisplayIdAssignedToUser(context, userManager); 1706 } 1707 1708 if (displayId != DEFAULT_DISPLAY) { 1709 if (DEBUG) { 1710 Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")"); 1711 } 1712 // Context is explicitly setting the display, so we respect that... 1713 return displayId; 1714 } 1715 // ...otherwise, we need to get the display the test's user is running on 1716 int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager); 1717 if (DEBUG) { 1718 Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")"); 1719 } 1720 return userDisplayId; 1721 } 1722 getMainDisplayIdAssignedToUser(Context context, UserManager userManager)1723 private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) { 1724 if (!userManager.isUserVisible()) { 1725 // Should also not happen, but ... 1726 Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using " 1727 + "DEFAULT_DISPLAY"); 1728 return DEFAULT_DISPLAY; 1729 } 1730 return userManager.getMainDisplayIdAssignedToUser(); 1731 } 1732 1733 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { 1734 IAccessibilityServiceClientImpl(Looper looper, int generationId)1735 public IAccessibilityServiceClientImpl(Looper looper, int generationId) { 1736 super(/* context= */ null, looper, new Callbacks() { 1737 private final int mGenerationId = generationId; 1738 1739 /** 1740 * True if UiAutomation doesn't interact with this client anymore. 1741 * Used by methods below to stop sending notifications or changing members 1742 * of {@link UiAutomation}. 1743 */ 1744 private boolean isGenerationChangedLocked() { 1745 return mGenerationId != UiAutomation.this.mGenerationId; 1746 } 1747 1748 @Override 1749 public void init(int connectionId, IBinder windowToken) { 1750 if (DEBUG) { 1751 Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken=" 1752 + windowToken + ", user=" + Process.myUserHandle() 1753 + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId 1754 + ", mGenerationId=" + mGenerationId 1755 + ", UiAutomation.mGenerationId=" 1756 + UiAutomation.this.mGenerationId); 1757 } 1758 synchronized (mLock) { 1759 if (isGenerationChangedLocked()) { 1760 if (DEBUG) { 1761 Log.d(LOG_TAG, "init(): returning because generation id changed"); 1762 } 1763 return; 1764 } 1765 if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTED"); 1766 mConnectionState = ConnectionState.CONNECTED; 1767 mConnectionId = connectionId; 1768 mLock.notifyAll(); 1769 } 1770 if (Build.IS_DEBUGGABLE) { 1771 Log.v(LOG_TAG, "Init " + UiAutomation.this); 1772 } 1773 } 1774 1775 @Override 1776 public void onServiceConnected() { 1777 /* do nothing */ 1778 } 1779 1780 @Override 1781 public void onInterrupt() { 1782 /* do nothing */ 1783 } 1784 1785 @Override 1786 public void onSystemActionsChanged() { 1787 /* do nothing */ 1788 } 1789 1790 @Override 1791 public void createImeSession(IAccessibilityInputMethodSessionCallback callback) { 1792 /* do nothing */ 1793 } 1794 1795 @Override 1796 public void startInput( 1797 @Nullable RemoteAccessibilityInputConnection inputConnection, 1798 @NonNull EditorInfo editorInfo, boolean restarting) { 1799 } 1800 1801 @Override 1802 public boolean onGesture(AccessibilityGestureEvent gestureEvent) { 1803 /* do nothing */ 1804 return false; 1805 } 1806 1807 public void onMotionEvent(MotionEvent event) { 1808 /* do nothing */ 1809 } 1810 1811 @Override 1812 public void onTouchStateChanged(int displayId, int state) { 1813 /* do nothing */ 1814 } 1815 1816 @Override 1817 public void onAccessibilityEvent(AccessibilityEvent event) { 1818 if (VERBOSE) { 1819 Log.v(LOG_TAG, "onAccessibilityEvent(" + Process.myUserHandle() + "): " 1820 + event); 1821 } 1822 1823 final OnAccessibilityEventListener listener; 1824 synchronized (mLock) { 1825 if (isGenerationChangedLocked()) { 1826 if (VERBOSE) { 1827 Log.v(LOG_TAG, "onAccessibilityEvent(): returning because " 1828 + "generation id changed (from " 1829 + UiAutomation.this.mGenerationId + " to " 1830 + mGenerationId + ")"); 1831 } 1832 return; 1833 } 1834 // It is not guaranteed that the accessibility framework sends events by the 1835 // order of event timestamp. 1836 mLastEventTimeMillis = Math.max(mLastEventTimeMillis, event.getEventTime()); 1837 if (mWaitingForEventDelivery) { 1838 mEventQueue.add(AccessibilityEvent.obtain(event)); 1839 } 1840 mLock.notifyAll(); 1841 listener = mOnAccessibilityEventListener; 1842 } 1843 if (listener != null) { 1844 // Calling out only without a lock held. 1845 mLocalCallbackHandler.sendMessage(PooledLambda.obtainMessage( 1846 OnAccessibilityEventListener::onAccessibilityEvent, 1847 listener, AccessibilityEvent.obtain(event))); 1848 } 1849 } 1850 1851 @Override 1852 public boolean onKeyEvent(KeyEvent event) { 1853 return false; 1854 } 1855 1856 @Override 1857 public void onMagnificationChanged(int displayId, @NonNull Region region, 1858 MagnificationConfig config) { 1859 /* do nothing */ 1860 } 1861 1862 @Override 1863 public void onSoftKeyboardShowModeChanged(int showMode) { 1864 /* do nothing */ 1865 } 1866 1867 @Override 1868 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) { 1869 /* do nothing */ 1870 } 1871 1872 @Override 1873 public void onFingerprintCapturingGesturesChanged(boolean active) { 1874 /* do nothing */ 1875 } 1876 1877 @Override 1878 public void onFingerprintGesture(int gesture) { 1879 /* do nothing */ 1880 } 1881 1882 @Override 1883 public void onAccessibilityButtonClicked(int displayId) { 1884 /* do nothing */ 1885 } 1886 1887 @Override 1888 public void onAccessibilityButtonAvailabilityChanged(boolean available) { 1889 /* do nothing */ 1890 } 1891 }); 1892 } 1893 } 1894 } 1895