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