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