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