UiAutomation.java revision f3e5d1d483231d615f5e77032f787fcd8047488b
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    public void destroy() {
327        disconnect();
328        mIsDestroyed = true;
329    }
330
331    /**
332     * Performs a global action. Such an action can be performed at any moment
333     * regardless of the current application or user location in that application.
334     * For example going back, going home, opening recents, etc.
335     *
336     * @param action The action to perform.
337     * @return Whether the action was successfully performed.
338     *
339     * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK
340     * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME
341     * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
342     * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS
343     */
344    public final boolean performGlobalAction(int action) {
345        final IAccessibilityServiceConnection connection;
346        synchronized (mLock) {
347            throwIfNotConnectedLocked();
348            connection = AccessibilityInteractionClient.getInstance()
349                    .getConnection(mConnectionId);
350        }
351        // Calling out without a lock held.
352        if (connection != null) {
353            try {
354                return connection.performGlobalAction(action);
355            } catch (RemoteException re) {
356                Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
357            }
358        }
359        return false;
360    }
361
362    /**
363     * Find the view that has the specified focus type. The search is performed
364     * across all windows.
365     * <p>
366     * <strong>Note:</strong> In order to access the windows you have to opt-in
367     * to retrieve the interactive windows by setting the
368     * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
369     * Otherwise, the search will be performed only in the active window.
370     * </p>
371     *
372     * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
373     *         {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
374     * @return The node info of the focused view or null.
375     *
376     * @see AccessibilityNodeInfo#FOCUS_INPUT
377     * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
378     */
379    public AccessibilityNodeInfo findFocus(int focus) {
380        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
381                AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
382    }
383
384    /**
385     * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
386     * This method is useful if one wants to change some of the dynamically
387     * configurable properties at runtime.
388     *
389     * @return The accessibility service info.
390     *
391     * @see AccessibilityServiceInfo
392     */
393    public final AccessibilityServiceInfo getServiceInfo() {
394        final IAccessibilityServiceConnection connection;
395        synchronized (mLock) {
396            throwIfNotConnectedLocked();
397            connection = AccessibilityInteractionClient.getInstance()
398                    .getConnection(mConnectionId);
399        }
400        // Calling out without a lock held.
401        if (connection != null) {
402            try {
403                return connection.getServiceInfo();
404            } catch (RemoteException re) {
405                Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
406            }
407        }
408        return null;
409    }
410
411    /**
412     * Sets the {@link AccessibilityServiceInfo} that describes how this
413     * UiAutomation will be handled by the platform accessibility layer.
414     *
415     * @param info The info.
416     *
417     * @see AccessibilityServiceInfo
418     */
419    public final void setServiceInfo(AccessibilityServiceInfo info) {
420        final IAccessibilityServiceConnection connection;
421        synchronized (mLock) {
422            throwIfNotConnectedLocked();
423            AccessibilityInteractionClient.getInstance().clearCache();
424            connection = AccessibilityInteractionClient.getInstance()
425                    .getConnection(mConnectionId);
426        }
427        // Calling out without a lock held.
428        if (connection != null) {
429            try {
430                connection.setServiceInfo(info);
431            } catch (RemoteException re) {
432                Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
433            }
434        }
435    }
436
437    /**
438     * Gets the windows on the screen. This method returns only the windows
439     * that a sighted user can interact with, as opposed to all windows.
440     * For example, if there is a modal dialog shown and the user cannot touch
441     * anything behind it, then only the modal window will be reported
442     * (assuming it is the top one). For convenience the returned windows
443     * are ordered in a descending layer order, which is the windows that
444     * are higher in the Z-order are reported first.
445     * <p>
446     * <strong>Note:</strong> In order to access the windows you have to opt-in
447     * to retrieve the interactive windows by setting the
448     * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
449     * </p>
450     *
451     * @return The windows if there are windows such, otherwise an empty list.
452     */
453    public List<AccessibilityWindowInfo> getWindows() {
454        final int connectionId;
455        synchronized (mLock) {
456            throwIfNotConnectedLocked();
457            connectionId = mConnectionId;
458        }
459        // Calling out without a lock held.
460        return AccessibilityInteractionClient.getInstance()
461                .getWindows(connectionId);
462    }
463
464    /**
465     * Gets the root {@link AccessibilityNodeInfo} in the active window.
466     *
467     * @return The root info.
468     */
469    public AccessibilityNodeInfo getRootInActiveWindow() {
470        final int connectionId;
471        synchronized (mLock) {
472            throwIfNotConnectedLocked();
473            connectionId = mConnectionId;
474        }
475        // Calling out without a lock held.
476        return AccessibilityInteractionClient.getInstance()
477                .getRootInActiveWindow(connectionId);
478    }
479
480    /**
481     * A method for injecting an arbitrary input event.
482     * <p>
483     * <strong>Note:</strong> It is caller's responsibility to recycle the event.
484     * </p>
485     * @param event The event to inject.
486     * @param sync Whether to inject the event synchronously.
487     * @return Whether event injection succeeded.
488     */
489    public boolean injectInputEvent(InputEvent event, boolean sync) {
490        synchronized (mLock) {
491            throwIfNotConnectedLocked();
492        }
493        try {
494            if (DEBUG) {
495                Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
496            }
497            // Calling out without a lock held.
498            return mUiAutomationConnection.injectInputEvent(event, sync);
499        } catch (RemoteException re) {
500            Log.e(LOG_TAG, "Error while injecting input event!", re);
501        }
502        return false;
503    }
504
505    /**
506     * Sets the device rotation. A client can freeze the rotation in
507     * desired state or freeze the rotation to its current state or
508     * unfreeze the rotation (rotating the device changes its rotation
509     * state).
510     *
511     * @param rotation The desired rotation.
512     * @return Whether the rotation was set successfully.
513     *
514     * @see #ROTATION_FREEZE_0
515     * @see #ROTATION_FREEZE_90
516     * @see #ROTATION_FREEZE_180
517     * @see #ROTATION_FREEZE_270
518     * @see #ROTATION_FREEZE_CURRENT
519     * @see #ROTATION_UNFREEZE
520     */
521    public boolean setRotation(int rotation) {
522        synchronized (mLock) {
523            throwIfNotConnectedLocked();
524        }
525        switch (rotation) {
526            case ROTATION_FREEZE_0:
527            case ROTATION_FREEZE_90:
528            case ROTATION_FREEZE_180:
529            case ROTATION_FREEZE_270:
530            case ROTATION_UNFREEZE:
531            case ROTATION_FREEZE_CURRENT: {
532                try {
533                    // Calling out without a lock held.
534                    mUiAutomationConnection.setRotation(rotation);
535                    return true;
536                } catch (RemoteException re) {
537                    Log.e(LOG_TAG, "Error while setting rotation!", re);
538                }
539            } return false;
540            default: {
541                throw new IllegalArgumentException("Invalid rotation.");
542            }
543        }
544    }
545
546    /**
547     * Executes a command and waits for a specific accessibility event up to a
548     * given wait timeout. To detect a sequence of events one can implement a
549     * filter that keeps track of seen events of the expected sequence and
550     * returns true after the last event of that sequence is received.
551     * <p>
552     * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
553     * </p>
554     * @param command The command to execute.
555     * @param filter Filter that recognizes the expected event.
556     * @param timeoutMillis The wait timeout in milliseconds.
557     *
558     * @throws TimeoutException If the expected event is not received within the timeout.
559     */
560    public AccessibilityEvent executeAndWaitForEvent(Runnable command,
561            AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
562        // Acquire the lock and prepare for receiving events.
563        synchronized (mLock) {
564            throwIfNotConnectedLocked();
565            mEventQueue.clear();
566            // Prepare to wait for an event.
567            mWaitingForEventDelivery = true;
568        }
569
570        // Note: We have to release the lock since calling out with this lock held
571        // can bite. We will correctly filter out events from other interactions,
572        // so starting to collect events before running the action is just fine.
573
574        // We will ignore events from previous interactions.
575        final long executionStartTimeMillis = SystemClock.uptimeMillis();
576        // Execute the command *without* the lock being held.
577        command.run();
578
579        // Acquire the lock and wait for the event.
580        synchronized (mLock) {
581            try {
582                // Wait for the event.
583                final long startTimeMillis = SystemClock.uptimeMillis();
584                while (true) {
585                    // Drain the event queue
586                    while (!mEventQueue.isEmpty()) {
587                        AccessibilityEvent event = mEventQueue.remove(0);
588                        // Ignore events from previous interactions.
589                        if (event.getEventTime() < executionStartTimeMillis) {
590                            continue;
591                        }
592                        if (filter.accept(event)) {
593                            return event;
594                        }
595                        event.recycle();
596                    }
597                    // Check if timed out and if not wait.
598                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
599                    final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
600                    if (remainingTimeMillis <= 0) {
601                        throw new TimeoutException("Expected event not received within: "
602                                + timeoutMillis + " ms.");
603                    }
604                    try {
605                        mLock.wait(remainingTimeMillis);
606                    } catch (InterruptedException ie) {
607                        /* ignore */
608                    }
609                }
610            } finally {
611                mWaitingForEventDelivery = false;
612                mEventQueue.clear();
613                mLock.notifyAll();
614            }
615        }
616    }
617
618    /**
619     * Waits for the accessibility event stream to become idle, which is not to
620     * have received an accessibility event within <code>idleTimeoutMillis</code>.
621     * The total time spent to wait for an idle accessibility event stream is bounded
622     * by the <code>globalTimeoutMillis</code>.
623     *
624     * @param idleTimeoutMillis The timeout in milliseconds between two events
625     *            to consider the device idle.
626     * @param globalTimeoutMillis The maximal global timeout in milliseconds in
627     *            which to wait for an idle state.
628     *
629     * @throws TimeoutException If no idle state was detected within
630     *            <code>globalTimeoutMillis.</code>
631     */
632    public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
633            throws TimeoutException {
634        synchronized (mLock) {
635            throwIfNotConnectedLocked();
636
637            final long startTimeMillis = SystemClock.uptimeMillis();
638            if (mLastEventTimeMillis <= 0) {
639                mLastEventTimeMillis = startTimeMillis;
640            }
641
642            while (true) {
643                final long currentTimeMillis = SystemClock.uptimeMillis();
644                // Did we get idle state within the global timeout?
645                final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
646                final long remainingGlobalTimeMillis =
647                        globalTimeoutMillis - elapsedGlobalTimeMillis;
648                if (remainingGlobalTimeMillis <= 0) {
649                    throw new TimeoutException("No idle state with idle timeout: "
650                            + idleTimeoutMillis + " within global timeout: "
651                            + globalTimeoutMillis);
652                }
653                // Did we get an idle state within the idle timeout?
654                final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
655                final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
656                if (remainingIdleTimeMillis <= 0) {
657                    return;
658                }
659                try {
660                     mLock.wait(remainingIdleTimeMillis);
661                } catch (InterruptedException ie) {
662                     /* ignore */
663                }
664            }
665        }
666    }
667
668    /**
669     * Takes a screenshot.
670     *
671     * @return The screenshot bitmap on success, null otherwise.
672     */
673    public Bitmap takeScreenshot() {
674        synchronized (mLock) {
675            throwIfNotConnectedLocked();
676        }
677        Display display = DisplayManagerGlobal.getInstance()
678                .getRealDisplay(Display.DEFAULT_DISPLAY);
679        Point displaySize = new Point();
680        display.getRealSize(displaySize);
681        final int displayWidth = displaySize.x;
682        final int displayHeight = displaySize.y;
683
684        final float screenshotWidth;
685        final float screenshotHeight;
686
687        final int rotation = display.getRotation();
688        switch (rotation) {
689            case ROTATION_FREEZE_0: {
690                screenshotWidth = displayWidth;
691                screenshotHeight = displayHeight;
692            } break;
693            case ROTATION_FREEZE_90: {
694                screenshotWidth = displayHeight;
695                screenshotHeight = displayWidth;
696            } break;
697            case ROTATION_FREEZE_180: {
698                screenshotWidth = displayWidth;
699                screenshotHeight = displayHeight;
700            } break;
701            case ROTATION_FREEZE_270: {
702                screenshotWidth = displayHeight;
703                screenshotHeight = displayWidth;
704            } break;
705            default: {
706                throw new IllegalArgumentException("Invalid rotation: "
707                        + rotation);
708            }
709        }
710
711        // Take the screenshot
712        Bitmap screenShot = null;
713        try {
714            // Calling out without a lock held.
715            screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
716                    (int) screenshotHeight);
717            if (screenShot == null) {
718                return null;
719            }
720        } catch (RemoteException re) {
721            Log.e(LOG_TAG, "Error while taking screnshot!", re);
722            return null;
723        }
724
725        // Rotate the screenshot to the current orientation
726        if (rotation != ROTATION_FREEZE_0) {
727            Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
728                    Bitmap.Config.ARGB_8888);
729            Canvas canvas = new Canvas(unrotatedScreenShot);
730            canvas.translate(unrotatedScreenShot.getWidth() / 2,
731                    unrotatedScreenShot.getHeight() / 2);
732            canvas.rotate(getDegreesForRotation(rotation));
733            canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
734            canvas.drawBitmap(screenShot, 0, 0, null);
735            canvas.setBitmap(null);
736            screenShot.recycle();
737            screenShot = unrotatedScreenShot;
738        }
739
740        // Optimization
741        screenShot.setHasAlpha(false);
742
743        return screenShot;
744    }
745
746    /**
747     * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
748     * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
749     * potentially undesirable actions such as calling 911 or posting on public forums etc.
750     *
751     * @param enable whether to run in a "monkey" mode or not. Default is not.
752     * @see ActivityManager#isUserAMonkey()
753     */
754    public void setRunAsMonkey(boolean enable) {
755        synchronized (mLock) {
756            throwIfNotConnectedLocked();
757        }
758        try {
759            ActivityManagerNative.getDefault().setUserIsMonkey(enable);
760        } catch (RemoteException re) {
761            Log.e(LOG_TAG, "Error while setting run as monkey!", re);
762        }
763    }
764
765    /**
766     * Clears the frame statistics for the content of a given window. These
767     * statistics contain information about the most recently rendered content
768     * frames.
769     *
770     * @param windowId The window id.
771     * @return Whether the window is present and its frame statistics
772     *         were cleared.
773     *
774     * @see android.view.WindowContentFrameStats
775     * @see #getWindowContentFrameStats(int)
776     * @see #getWindows()
777     * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
778     */
779    public boolean clearWindowContentFrameStats(int windowId) {
780        synchronized (mLock) {
781            throwIfNotConnectedLocked();
782        }
783        try {
784            if (DEBUG) {
785                Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
786            }
787            // Calling out without a lock held.
788            return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
789        } catch (RemoteException re) {
790            Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
791        }
792        return false;
793    }
794
795    /**
796     * Gets the frame statistics for a given window. These statistics contain
797     * information about the most recently rendered content frames.
798     * <p>
799     * A typical usage requires clearing the window frame statistics via {@link
800     * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
801     * finally getting the window frame statistics via calling this method.
802     * </p>
803     * <pre>
804     * // Assume we have at least one window.
805     * final int windowId = getWindows().get(0).getId();
806     *
807     * // Start with a clean slate.
808     * uiAutimation.clearWindowContentFrameStats(windowId);
809     *
810     * // Do stuff with the UI.
811     *
812     * // Get the frame statistics.
813     * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
814     * </pre>
815     *
816     * @param windowId The window id.
817     * @return The window frame statistics, or null if the window is not present.
818     *
819     * @see android.view.WindowContentFrameStats
820     * @see #clearWindowContentFrameStats(int)
821     * @see #getWindows()
822     * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
823     */
824    public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
825        synchronized (mLock) {
826            throwIfNotConnectedLocked();
827        }
828        try {
829            if (DEBUG) {
830                Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
831            }
832            // Calling out without a lock held.
833            return mUiAutomationConnection.getWindowContentFrameStats(windowId);
834        } catch (RemoteException re) {
835            Log.e(LOG_TAG, "Error getting window content frame stats!", re);
836        }
837        return null;
838    }
839
840    /**
841     * Clears the window animation rendering statistics. These statistics contain
842     * information about the most recently rendered window animation frames, i.e.
843     * for window transition animations.
844     *
845     * @see android.view.WindowAnimationFrameStats
846     * @see #getWindowAnimationFrameStats()
847     * @see android.R.styleable#WindowAnimation
848     */
849    public void clearWindowAnimationFrameStats() {
850        synchronized (mLock) {
851            throwIfNotConnectedLocked();
852        }
853        try {
854            if (DEBUG) {
855                Log.i(LOG_TAG, "Clearing window animation frame stats");
856            }
857            // Calling out without a lock held.
858            mUiAutomationConnection.clearWindowAnimationFrameStats();
859        } catch (RemoteException re) {
860            Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
861        }
862    }
863
864    /**
865     * Gets the window animation frame statistics. These statistics contain
866     * information about the most recently rendered window animation frames, i.e.
867     * for window transition animations.
868     *
869     * <p>
870     * A typical usage requires clearing the window animation frame statistics via
871     * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
872     * a window transition which uses a window animation and finally getting the window
873     * animation frame statistics by calling this method.
874     * </p>
875     * <pre>
876     * // Start with a clean slate.
877     * uiAutimation.clearWindowAnimationFrameStats();
878     *
879     * // Do stuff to trigger a window transition.
880     *
881     * // Get the frame statistics.
882     * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
883     * </pre>
884     *
885     * @return The window animation frame statistics.
886     *
887     * @see android.view.WindowAnimationFrameStats
888     * @see #clearWindowAnimationFrameStats()
889     * @see android.R.styleable#WindowAnimation
890     */
891    public WindowAnimationFrameStats getWindowAnimationFrameStats() {
892        synchronized (mLock) {
893            throwIfNotConnectedLocked();
894        }
895        try {
896            if (DEBUG) {
897                Log.i(LOG_TAG, "Getting window animation frame stats");
898            }
899            // Calling out without a lock held.
900            return mUiAutomationConnection.getWindowAnimationFrameStats();
901        } catch (RemoteException re) {
902            Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
903        }
904        return null;
905    }
906
907    /**
908     * Grants a runtime permission to a package for a user.
909     * @param packageName The package to which to grant.
910     * @param permission The permission to grant.
911     * @return Whether granting succeeded.
912     *
913     * @hide
914     */
915    @TestApi
916    public boolean grantRuntimePermission(String packageName, String permission,
917            UserHandle userHandle) {
918        synchronized (mLock) {
919            throwIfNotConnectedLocked();
920        }
921        try {
922            if (DEBUG) {
923                Log.i(LOG_TAG, "Granting runtime permission");
924            }
925            // Calling out without a lock held.
926            mUiAutomationConnection.grantRuntimePermission(packageName,
927                    permission, userHandle.getIdentifier());
928            // TODO: The package manager API should return boolean.
929            return true;
930        } catch (RemoteException re) {
931            Log.e(LOG_TAG, "Error granting runtime permission", re);
932        }
933        return false;
934    }
935
936    /**
937     * Revokes a runtime permission from a package for a user.
938     * @param packageName The package from which to revoke.
939     * @param permission The permission to revoke.
940     * @return Whether revoking succeeded.
941     *
942     * @hide
943     */
944    @TestApi
945    public boolean revokeRuntimePermission(String packageName, String permission,
946            UserHandle userHandle) {
947        synchronized (mLock) {
948            throwIfNotConnectedLocked();
949        }
950        try {
951            if (DEBUG) {
952                Log.i(LOG_TAG, "Revoking runtime permission");
953            }
954            // Calling out without a lock held.
955            mUiAutomationConnection.revokeRuntimePermission(packageName,
956                    permission, userHandle.getIdentifier());
957            // TODO: The package manager API should return boolean.
958            return true;
959        } catch (RemoteException re) {
960            Log.e(LOG_TAG, "Error revoking runtime permission", re);
961        }
962        return false;
963    }
964
965    /**
966     * Executes a shell command. This method returs a file descriptor that points
967     * to the standard output stream. The command execution is similar to running
968     * "adb shell <command>" from a host connected to the device.
969     * <p>
970     * <strong>Note:</strong> It is your responsibility to close the retunred file
971     * descriptor once you are done reading.
972     * </p>
973     *
974     * @param command The command to execute.
975     * @return A file descriptor to the standard output stream.
976     */
977    public ParcelFileDescriptor executeShellCommand(String command) {
978        synchronized (mLock) {
979            throwIfNotConnectedLocked();
980        }
981
982        ParcelFileDescriptor source = null;
983        ParcelFileDescriptor sink = null;
984
985        try {
986            ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
987            source = pipe[0];
988            sink = pipe[1];
989
990            // Calling out without a lock held.
991            mUiAutomationConnection.executeShellCommand(command, sink);
992        } catch (IOException ioe) {
993            Log.e(LOG_TAG, "Error executing shell command!", ioe);
994        } catch (RemoteException re) {
995            Log.e(LOG_TAG, "Error executing shell command!", re);
996        } finally {
997            IoUtils.closeQuietly(sink);
998        }
999
1000        return source;
1001    }
1002
1003    private static float getDegreesForRotation(int value) {
1004        switch (value) {
1005            case Surface.ROTATION_90: {
1006                return 360f - 90f;
1007            }
1008            case Surface.ROTATION_180: {
1009                return 360f - 180f;
1010            }
1011            case Surface.ROTATION_270: {
1012                return 360f - 270f;
1013            } default: {
1014                return 0;
1015            }
1016        }
1017    }
1018
1019    private boolean isConnectedLocked() {
1020        return mConnectionId != CONNECTION_ID_UNDEFINED;
1021    }
1022
1023    private void throwIfConnectedLocked() {
1024        if (mConnectionId != CONNECTION_ID_UNDEFINED) {
1025            throw new IllegalStateException("UiAutomation not connected!");
1026        }
1027    }
1028
1029    private void throwIfNotConnectedLocked() {
1030        if (!isConnectedLocked()) {
1031            throw new IllegalStateException("UiAutomation not connected!");
1032        }
1033    }
1034
1035    private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
1036
1037        public IAccessibilityServiceClientImpl(Looper looper) {
1038            super(null, looper, new Callbacks() {
1039                @Override
1040                public void init(int connectionId, IBinder windowToken) {
1041                    synchronized (mLock) {
1042                        mConnectionId = connectionId;
1043                        mLock.notifyAll();
1044                    }
1045                }
1046
1047                @Override
1048                public void onServiceConnected() {
1049                    /* do nothing */
1050                }
1051
1052                @Override
1053                public void onInterrupt() {
1054                    /* do nothing */
1055                }
1056
1057                @Override
1058                public boolean onGesture(int gestureId) {
1059                    /* do nothing */
1060                    return false;
1061                }
1062
1063                @Override
1064                public void onAccessibilityEvent(AccessibilityEvent event) {
1065                    synchronized (mLock) {
1066                        mLastEventTimeMillis = event.getEventTime();
1067                        if (mWaitingForEventDelivery) {
1068                            mEventQueue.add(AccessibilityEvent.obtain(event));
1069                        }
1070                        mLock.notifyAll();
1071                    }
1072                    // Calling out only without a lock held.
1073                    final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
1074                    if (listener != null) {
1075                        listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
1076                    }
1077                }
1078
1079                @Override
1080                public boolean onKeyEvent(KeyEvent event) {
1081                    return false;
1082                }
1083
1084                @Override
1085                public void onMagnificationChanged(@NonNull Region region,
1086                        float scale, float centerX, float centerY) {
1087                    /* do nothing */
1088                }
1089
1090                @Override
1091                public void onSoftKeyboardShowModeChanged(int showMode) {
1092                    /* do nothing */
1093                }
1094
1095                @Override
1096                public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
1097                    /* do nothing */
1098                }
1099            });
1100        }
1101    }
1102}
1103