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