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