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