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