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