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