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