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                 AccessibilityNodeInfo.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         synchronized (mLock) {
584             try {
585                 // Wait for the event.
586                 final long startTimeMillis = SystemClock.uptimeMillis();
587                 while (true) {
588                     // Drain the event queue
589                     while (!mEventQueue.isEmpty()) {
590                         AccessibilityEvent event = mEventQueue.remove(0);
591                         // Ignore events from previous interactions.
592                         if (event.getEventTime() < executionStartTimeMillis) {
593                             continue;
594                         }
595                         if (filter.accept(event)) {
596                             return event;
597                         }
598                         event.recycle();
599                     }
600                     // Check if timed out and if not wait.
601                     final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
602                     final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
603                     if (remainingTimeMillis <= 0) {
604                         throw new TimeoutException("Expected event not received within: "
605                                 + timeoutMillis + " ms.");
606                     }
607                     try {
608                         mLock.wait(remainingTimeMillis);
609                     } catch (InterruptedException ie) {
610                         /* ignore */
611                     }
612                 }
613             } finally {
614                 mWaitingForEventDelivery = false;
615                 mEventQueue.clear();
616                 mLock.notifyAll();
617             }
618         }
619     }
620 
621     /**
622      * Waits for the accessibility event stream to become idle, which is not to
623      * have received an accessibility event within <code>idleTimeoutMillis</code>.
624      * The total time spent to wait for an idle accessibility event stream is bounded
625      * by the <code>globalTimeoutMillis</code>.
626      *
627      * @param idleTimeoutMillis The timeout in milliseconds between two events
628      *            to consider the device idle.
629      * @param globalTimeoutMillis The maximal global timeout in milliseconds in
630      *            which to wait for an idle state.
631      *
632      * @throws TimeoutException If no idle state was detected within
633      *            <code>globalTimeoutMillis.</code>
634      */
waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)635     public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
636             throws TimeoutException {
637         synchronized (mLock) {
638             throwIfNotConnectedLocked();
639 
640             final long startTimeMillis = SystemClock.uptimeMillis();
641             if (mLastEventTimeMillis <= 0) {
642                 mLastEventTimeMillis = startTimeMillis;
643             }
644 
645             while (true) {
646                 final long currentTimeMillis = SystemClock.uptimeMillis();
647                 // Did we get idle state within the global timeout?
648                 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
649                 final long remainingGlobalTimeMillis =
650                         globalTimeoutMillis - elapsedGlobalTimeMillis;
651                 if (remainingGlobalTimeMillis <= 0) {
652                     throw new TimeoutException("No idle state with idle timeout: "
653                             + idleTimeoutMillis + " within global timeout: "
654                             + globalTimeoutMillis);
655                 }
656                 // Did we get an idle state within the idle timeout?
657                 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
658                 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
659                 if (remainingIdleTimeMillis <= 0) {
660                     return;
661                 }
662                 try {
663                      mLock.wait(remainingIdleTimeMillis);
664                 } catch (InterruptedException ie) {
665                      /* ignore */
666                 }
667             }
668         }
669     }
670 
671     /**
672      * Takes a screenshot.
673      *
674      * @return The screenshot bitmap on success, null otherwise.
675      */
takeScreenshot()676     public Bitmap takeScreenshot() {
677         synchronized (mLock) {
678             throwIfNotConnectedLocked();
679         }
680         Display display = DisplayManagerGlobal.getInstance()
681                 .getRealDisplay(Display.DEFAULT_DISPLAY);
682         Point displaySize = new Point();
683         display.getRealSize(displaySize);
684         final int displayWidth = displaySize.x;
685         final int displayHeight = displaySize.y;
686 
687         final float screenshotWidth;
688         final float screenshotHeight;
689 
690         final int rotation = display.getRotation();
691         switch (rotation) {
692             case ROTATION_FREEZE_0: {
693                 screenshotWidth = displayWidth;
694                 screenshotHeight = displayHeight;
695             } break;
696             case ROTATION_FREEZE_90: {
697                 screenshotWidth = displayHeight;
698                 screenshotHeight = displayWidth;
699             } break;
700             case ROTATION_FREEZE_180: {
701                 screenshotWidth = displayWidth;
702                 screenshotHeight = displayHeight;
703             } break;
704             case ROTATION_FREEZE_270: {
705                 screenshotWidth = displayHeight;
706                 screenshotHeight = displayWidth;
707             } break;
708             default: {
709                 throw new IllegalArgumentException("Invalid rotation: "
710                         + rotation);
711             }
712         }
713 
714         // Take the screenshot
715         Bitmap screenShot = null;
716         try {
717             // Calling out without a lock held.
718             screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
719                     (int) screenshotHeight);
720             if (screenShot == null) {
721                 return null;
722             }
723         } catch (RemoteException re) {
724             Log.e(LOG_TAG, "Error while taking screnshot!", re);
725             return null;
726         }
727 
728         // Rotate the screenshot to the current orientation
729         if (rotation != ROTATION_FREEZE_0) {
730             Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
731                     Bitmap.Config.ARGB_8888);
732             Canvas canvas = new Canvas(unrotatedScreenShot);
733             canvas.translate(unrotatedScreenShot.getWidth() / 2,
734                     unrotatedScreenShot.getHeight() / 2);
735             canvas.rotate(getDegreesForRotation(rotation));
736             canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
737             canvas.drawBitmap(screenShot, 0, 0, null);
738             canvas.setBitmap(null);
739             screenShot.recycle();
740             screenShot = unrotatedScreenShot;
741         }
742 
743         // Optimization
744         screenShot.setHasAlpha(false);
745 
746         return screenShot;
747     }
748 
749     /**
750      * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
751      * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
752      * potentially undesirable actions such as calling 911 or posting on public forums etc.
753      *
754      * @param enable whether to run in a "monkey" mode or not. Default is not.
755      * @see ActivityManager#isUserAMonkey()
756      */
setRunAsMonkey(boolean enable)757     public void setRunAsMonkey(boolean enable) {
758         synchronized (mLock) {
759             throwIfNotConnectedLocked();
760         }
761         try {
762             ActivityManagerNative.getDefault().setUserIsMonkey(enable);
763         } catch (RemoteException re) {
764             Log.e(LOG_TAG, "Error while setting run as monkey!", re);
765         }
766     }
767 
768     /**
769      * Clears the frame statistics for the content of a given window. These
770      * statistics contain information about the most recently rendered content
771      * frames.
772      *
773      * @param windowId The window id.
774      * @return Whether the window is present and its frame statistics
775      *         were cleared.
776      *
777      * @see android.view.WindowContentFrameStats
778      * @see #getWindowContentFrameStats(int)
779      * @see #getWindows()
780      * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
781      */
clearWindowContentFrameStats(int windowId)782     public boolean clearWindowContentFrameStats(int windowId) {
783         synchronized (mLock) {
784             throwIfNotConnectedLocked();
785         }
786         try {
787             if (DEBUG) {
788                 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
789             }
790             // Calling out without a lock held.
791             return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
792         } catch (RemoteException re) {
793             Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
794         }
795         return false;
796     }
797 
798     /**
799      * Gets the frame statistics for a given window. These statistics contain
800      * information about the most recently rendered content frames.
801      * <p>
802      * A typical usage requires clearing the window frame statistics via {@link
803      * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
804      * finally getting the window frame statistics via calling this method.
805      * </p>
806      * <pre>
807      * // Assume we have at least one window.
808      * final int windowId = getWindows().get(0).getId();
809      *
810      * // Start with a clean slate.
811      * uiAutimation.clearWindowContentFrameStats(windowId);
812      *
813      * // Do stuff with the UI.
814      *
815      * // Get the frame statistics.
816      * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
817      * </pre>
818      *
819      * @param windowId The window id.
820      * @return The window frame statistics, or null if the window is not present.
821      *
822      * @see android.view.WindowContentFrameStats
823      * @see #clearWindowContentFrameStats(int)
824      * @see #getWindows()
825      * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
826      */
getWindowContentFrameStats(int windowId)827     public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
828         synchronized (mLock) {
829             throwIfNotConnectedLocked();
830         }
831         try {
832             if (DEBUG) {
833                 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
834             }
835             // Calling out without a lock held.
836             return mUiAutomationConnection.getWindowContentFrameStats(windowId);
837         } catch (RemoteException re) {
838             Log.e(LOG_TAG, "Error getting window content frame stats!", re);
839         }
840         return null;
841     }
842 
843     /**
844      * Clears the window animation rendering statistics. These statistics contain
845      * information about the most recently rendered window animation frames, i.e.
846      * for window transition animations.
847      *
848      * @see android.view.WindowAnimationFrameStats
849      * @see #getWindowAnimationFrameStats()
850      * @see android.R.styleable#WindowAnimation
851      */
clearWindowAnimationFrameStats()852     public void clearWindowAnimationFrameStats() {
853         synchronized (mLock) {
854             throwIfNotConnectedLocked();
855         }
856         try {
857             if (DEBUG) {
858                 Log.i(LOG_TAG, "Clearing window animation frame stats");
859             }
860             // Calling out without a lock held.
861             mUiAutomationConnection.clearWindowAnimationFrameStats();
862         } catch (RemoteException re) {
863             Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
864         }
865     }
866 
867     /**
868      * Gets the window animation frame statistics. These statistics contain
869      * information about the most recently rendered window animation frames, i.e.
870      * for window transition animations.
871      *
872      * <p>
873      * A typical usage requires clearing the window animation frame statistics via
874      * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
875      * a window transition which uses a window animation and finally getting the window
876      * animation frame statistics by calling this method.
877      * </p>
878      * <pre>
879      * // Start with a clean slate.
880      * uiAutimation.clearWindowAnimationFrameStats();
881      *
882      * // Do stuff to trigger a window transition.
883      *
884      * // Get the frame statistics.
885      * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
886      * </pre>
887      *
888      * @return The window animation frame statistics.
889      *
890      * @see android.view.WindowAnimationFrameStats
891      * @see #clearWindowAnimationFrameStats()
892      * @see android.R.styleable#WindowAnimation
893      */
getWindowAnimationFrameStats()894     public WindowAnimationFrameStats getWindowAnimationFrameStats() {
895         synchronized (mLock) {
896             throwIfNotConnectedLocked();
897         }
898         try {
899             if (DEBUG) {
900                 Log.i(LOG_TAG, "Getting window animation frame stats");
901             }
902             // Calling out without a lock held.
903             return mUiAutomationConnection.getWindowAnimationFrameStats();
904         } catch (RemoteException re) {
905             Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
906         }
907         return null;
908     }
909 
910     /**
911      * Grants a runtime permission to a package for a user.
912      * @param packageName The package to which to grant.
913      * @param permission The permission to grant.
914      * @return Whether granting succeeded.
915      *
916      * @hide
917      */
918     @TestApi
grantRuntimePermission(String packageName, String permission, UserHandle userHandle)919     public boolean grantRuntimePermission(String packageName, String permission,
920             UserHandle userHandle) {
921         synchronized (mLock) {
922             throwIfNotConnectedLocked();
923         }
924         try {
925             if (DEBUG) {
926                 Log.i(LOG_TAG, "Granting runtime permission");
927             }
928             // Calling out without a lock held.
929             mUiAutomationConnection.grantRuntimePermission(packageName,
930                     permission, userHandle.getIdentifier());
931             // TODO: The package manager API should return boolean.
932             return true;
933         } catch (RemoteException re) {
934             Log.e(LOG_TAG, "Error granting runtime permission", re);
935         }
936         return false;
937     }
938 
939     /**
940      * Revokes a runtime permission from a package for a user.
941      * @param packageName The package from which to revoke.
942      * @param permission The permission to revoke.
943      * @return Whether revoking succeeded.
944      *
945      * @hide
946      */
947     @TestApi
revokeRuntimePermission(String packageName, String permission, UserHandle userHandle)948     public boolean revokeRuntimePermission(String packageName, String permission,
949             UserHandle userHandle) {
950         synchronized (mLock) {
951             throwIfNotConnectedLocked();
952         }
953         try {
954             if (DEBUG) {
955                 Log.i(LOG_TAG, "Revoking runtime permission");
956             }
957             // Calling out without a lock held.
958             mUiAutomationConnection.revokeRuntimePermission(packageName,
959                     permission, userHandle.getIdentifier());
960             // TODO: The package manager API should return boolean.
961             return true;
962         } catch (RemoteException re) {
963             Log.e(LOG_TAG, "Error revoking runtime permission", re);
964         }
965         return false;
966     }
967 
968     /**
969      * Executes a shell command. This method returs a file descriptor that points
970      * to the standard output stream. The command execution is similar to running
971      * "adb shell <command>" from a host connected to the device.
972      * <p>
973      * <strong>Note:</strong> It is your responsibility to close the retunred file
974      * descriptor once you are done reading.
975      * </p>
976      *
977      * @param command The command to execute.
978      * @return A file descriptor to the standard output stream.
979      */
executeShellCommand(String command)980     public ParcelFileDescriptor executeShellCommand(String command) {
981         synchronized (mLock) {
982             throwIfNotConnectedLocked();
983         }
984 
985         ParcelFileDescriptor source = null;
986         ParcelFileDescriptor sink = null;
987 
988         try {
989             ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
990             source = pipe[0];
991             sink = pipe[1];
992 
993             // Calling out without a lock held.
994             mUiAutomationConnection.executeShellCommand(command, sink);
995         } catch (IOException ioe) {
996             Log.e(LOG_TAG, "Error executing shell command!", ioe);
997         } catch (RemoteException re) {
998             Log.e(LOG_TAG, "Error executing shell command!", re);
999         } finally {
1000             IoUtils.closeQuietly(sink);
1001         }
1002 
1003         return source;
1004     }
1005 
getDegreesForRotation(int value)1006     private static float getDegreesForRotation(int value) {
1007         switch (value) {
1008             case Surface.ROTATION_90: {
1009                 return 360f - 90f;
1010             }
1011             case Surface.ROTATION_180: {
1012                 return 360f - 180f;
1013             }
1014             case Surface.ROTATION_270: {
1015                 return 360f - 270f;
1016             } default: {
1017                 return 0;
1018             }
1019         }
1020     }
1021 
isConnectedLocked()1022     private boolean isConnectedLocked() {
1023         return mConnectionId != CONNECTION_ID_UNDEFINED;
1024     }
1025 
throwIfConnectedLocked()1026     private void throwIfConnectedLocked() {
1027         if (mConnectionId != CONNECTION_ID_UNDEFINED) {
1028             throw new IllegalStateException("UiAutomation not connected!");
1029         }
1030     }
1031 
throwIfNotConnectedLocked()1032     private void throwIfNotConnectedLocked() {
1033         if (!isConnectedLocked()) {
1034             throw new IllegalStateException("UiAutomation not connected!");
1035         }
1036     }
1037 
1038     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
1039 
IAccessibilityServiceClientImpl(Looper looper)1040         public IAccessibilityServiceClientImpl(Looper looper) {
1041             super(null, looper, new Callbacks() {
1042                 @Override
1043                 public void init(int connectionId, IBinder windowToken) {
1044                     synchronized (mLock) {
1045                         mConnectionId = connectionId;
1046                         mLock.notifyAll();
1047                     }
1048                 }
1049 
1050                 @Override
1051                 public void onServiceConnected() {
1052                     /* do nothing */
1053                 }
1054 
1055                 @Override
1056                 public void onInterrupt() {
1057                     /* do nothing */
1058                 }
1059 
1060                 @Override
1061                 public boolean onGesture(int gestureId) {
1062                     /* do nothing */
1063                     return false;
1064                 }
1065 
1066                 @Override
1067                 public void onAccessibilityEvent(AccessibilityEvent event) {
1068                     synchronized (mLock) {
1069                         mLastEventTimeMillis = event.getEventTime();
1070                         if (mWaitingForEventDelivery) {
1071                             mEventQueue.add(AccessibilityEvent.obtain(event));
1072                         }
1073                         mLock.notifyAll();
1074                     }
1075                     // Calling out only without a lock held.
1076                     final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
1077                     if (listener != null) {
1078                         listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
1079                     }
1080                 }
1081 
1082                 @Override
1083                 public boolean onKeyEvent(KeyEvent event) {
1084                     return false;
1085                 }
1086 
1087                 @Override
1088                 public void onMagnificationChanged(@NonNull Region region,
1089                         float scale, float centerX, float centerY) {
1090                     /* do nothing */
1091                 }
1092 
1093                 @Override
1094                 public void onSoftKeyboardShowModeChanged(int showMode) {
1095                     /* do nothing */
1096                 }
1097 
1098                 @Override
1099                 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
1100                     /* do nothing */
1101                 }
1102             });
1103         }
1104     }
1105 }
1106