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