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