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