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