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
17package android.app;
18
19import android.accessibilityservice.AccessibilityService.Callbacks;
20import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
21import android.accessibilityservice.AccessibilityServiceInfo;
22import android.accessibilityservice.IAccessibilityServiceClient;
23import android.accessibilityservice.IAccessibilityServiceConnection;
24import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Point;
27import android.hardware.display.DisplayManagerGlobal;
28import android.os.IBinder;
29import android.os.Looper;
30import android.os.ParcelFileDescriptor;
31import android.os.RemoteException;
32import android.os.SystemClock;
33import android.os.UserHandle;
34import android.util.Log;
35import android.view.Display;
36import android.view.InputEvent;
37import android.view.KeyEvent;
38import android.view.Surface;
39import android.view.WindowAnimationFrameStats;
40import android.view.WindowContentFrameStats;
41import android.view.accessibility.AccessibilityEvent;
42import android.view.accessibility.AccessibilityInteractionClient;
43import android.view.accessibility.AccessibilityNodeInfo;
44import android.view.accessibility.AccessibilityWindowInfo;
45import android.view.accessibility.IAccessibilityInteractionConnection;
46import libcore.io.IoUtils;
47
48import java.io.IOException;
49import java.util.ArrayList;
50import java.util.List;
51import 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 */
77public 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         */
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         */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
959    private boolean isConnectedLocked() {
960        return mConnectionId != CONNECTION_ID_UNDEFINED;
961    }
962
963    private void throwIfConnectedLocked() {
964        if (mConnectionId != CONNECTION_ID_UNDEFINED) {
965            throw new IllegalStateException("UiAutomation not connected!");
966        }
967    }
968
969    private void throwIfNotConnectedLocked() {
970        if (!isConnectedLocked()) {
971            throw new IllegalStateException("UiAutomation not connected!");
972        }
973    }
974
975    private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
976
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