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