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