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                 AccessibilityWindowInfo.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         try {
    584             // Wait for the event.
    585             final long startTimeMillis = SystemClock.uptimeMillis();
    586             while (true) {
    587                 List<AccessibilityEvent> localEvents = new ArrayList<>();
    588                 synchronized (mLock) {
    589                     localEvents.addAll(mEventQueue);
    590                     mEventQueue.clear();
    591                 }
    592                 // Drain the event queue
    593                 while (!localEvents.isEmpty()) {
    594                     AccessibilityEvent event = localEvents.remove(0);
    595                     // Ignore events from previous interactions.
    596                     if (event.getEventTime() < executionStartTimeMillis) {
    597                         continue;
    598                     }
    599                     if (filter.accept(event)) {
    600                         return event;
    601                     }
    602                     event.recycle();
    603                 }
    604                 // Check if timed out and if not wait.
    605                 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
    606                 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
    607                 if (remainingTimeMillis <= 0) {
    608                     throw new TimeoutException("Expected event not received within: "
    609                             + timeoutMillis + " ms.");
    610                 }
    611                 synchronized (mLock) {
    612                     if (mEventQueue.isEmpty()) {
    613                         try {
    614                             mLock.wait(remainingTimeMillis);
    615                         } catch (InterruptedException ie) {
    616                             /* ignore */
    617                         }
    618                     }
    619                 }
    620             }
    621         } finally {
    622             synchronized (mLock) {
    623                 mWaitingForEventDelivery = false;
    624                 mEventQueue.clear();
    625                 mLock.notifyAll();
    626             }
    627         }
    628     }
    629 
    630     /**
    631      * Waits for the accessibility event stream to become idle, which is not to
    632      * have received an accessibility event within <code>idleTimeoutMillis</code>.
    633      * The total time spent to wait for an idle accessibility event stream is bounded
    634      * by the <code>globalTimeoutMillis</code>.
    635      *
    636      * @param idleTimeoutMillis The timeout in milliseconds between two events
    637      *            to consider the device idle.
    638      * @param globalTimeoutMillis The maximal global timeout in milliseconds in
    639      *            which to wait for an idle state.
    640      *
    641      * @throws TimeoutException If no idle state was detected within
    642      *            <code>globalTimeoutMillis.</code>
    643      */
    644     public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
    645             throws TimeoutException {
    646         synchronized (mLock) {
    647             throwIfNotConnectedLocked();
    648 
    649             final long startTimeMillis = SystemClock.uptimeMillis();
    650             if (mLastEventTimeMillis <= 0) {
    651                 mLastEventTimeMillis = startTimeMillis;
    652             }
    653 
    654             while (true) {
    655                 final long currentTimeMillis = SystemClock.uptimeMillis();
    656                 // Did we get idle state within the global timeout?
    657                 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
    658                 final long remainingGlobalTimeMillis =
    659                         globalTimeoutMillis - elapsedGlobalTimeMillis;
    660                 if (remainingGlobalTimeMillis <= 0) {
    661                     throw new TimeoutException("No idle state with idle timeout: "
    662                             + idleTimeoutMillis + " within global timeout: "
    663                             + globalTimeoutMillis);
    664                 }
    665                 // Did we get an idle state within the idle timeout?
    666                 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
    667                 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
    668                 if (remainingIdleTimeMillis <= 0) {
    669                     return;
    670                 }
    671                 try {
    672                      mLock.wait(remainingIdleTimeMillis);
    673                 } catch (InterruptedException ie) {
    674                      /* ignore */
    675                 }
    676             }
    677         }
    678     }
    679 
    680     /**
    681      * Takes a screenshot.
    682      *
    683      * @return The screenshot bitmap on success, null otherwise.
    684      */
    685     public Bitmap takeScreenshot() {
    686         synchronized (mLock) {
    687             throwIfNotConnectedLocked();
    688         }
    689         Display display = DisplayManagerGlobal.getInstance()
    690                 .getRealDisplay(Display.DEFAULT_DISPLAY);
    691         Point displaySize = new Point();
    692         display.getRealSize(displaySize);
    693         final int displayWidth = displaySize.x;
    694         final int displayHeight = displaySize.y;
    695 
    696         final float screenshotWidth;
    697         final float screenshotHeight;
    698 
    699         final int rotation = display.getRotation();
    700         switch (rotation) {
    701             case ROTATION_FREEZE_0: {
    702                 screenshotWidth = displayWidth;
    703                 screenshotHeight = displayHeight;
    704             } break;
    705             case ROTATION_FREEZE_90: {
    706                 screenshotWidth = displayHeight;
    707                 screenshotHeight = displayWidth;
    708             } break;
    709             case ROTATION_FREEZE_180: {
    710                 screenshotWidth = displayWidth;
    711                 screenshotHeight = displayHeight;
    712             } break;
    713             case ROTATION_FREEZE_270: {
    714                 screenshotWidth = displayHeight;
    715                 screenshotHeight = displayWidth;
    716             } break;
    717             default: {
    718                 throw new IllegalArgumentException("Invalid rotation: "
    719                         + rotation);
    720             }
    721         }
    722 
    723         // Take the screenshot
    724         Bitmap screenShot = null;
    725         try {
    726             // Calling out without a lock held.
    727             screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
    728                     (int) screenshotHeight);
    729             if (screenShot == null) {
    730                 return null;
    731             }
    732         } catch (RemoteException re) {
    733             Log.e(LOG_TAG, "Error while taking screnshot!", re);
    734             return null;
    735         }
    736 
    737         // Rotate the screenshot to the current orientation
    738         if (rotation != ROTATION_FREEZE_0) {
    739             Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
    740                     Bitmap.Config.ARGB_8888);
    741             Canvas canvas = new Canvas(unrotatedScreenShot);
    742             canvas.translate(unrotatedScreenShot.getWidth() / 2,
    743                     unrotatedScreenShot.getHeight() / 2);
    744             canvas.rotate(getDegreesForRotation(rotation));
    745             canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
    746             canvas.drawBitmap(screenShot, 0, 0, null);
    747             canvas.setBitmap(null);
    748             screenShot.recycle();
    749             screenShot = unrotatedScreenShot;
    750         }
    751 
    752         // Optimization
    753         screenShot.setHasAlpha(false);
    754 
    755         return screenShot;
    756     }
    757 
    758     /**
    759      * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
    760      * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
    761      * potentially undesirable actions such as calling 911 or posting on public forums etc.
    762      *
    763      * @param enable whether to run in a "monkey" mode or not. Default is not.
    764      * @see ActivityManager#isUserAMonkey()
    765      */
    766     public void setRunAsMonkey(boolean enable) {
    767         synchronized (mLock) {
    768             throwIfNotConnectedLocked();
    769         }
    770         try {
    771             ActivityManager.getService().setUserIsMonkey(enable);
    772         } catch (RemoteException re) {
    773             Log.e(LOG_TAG, "Error while setting run as monkey!", re);
    774         }
    775     }
    776 
    777     /**
    778      * Clears the frame statistics for the content of a given window. These
    779      * statistics contain information about the most recently rendered content
    780      * frames.
    781      *
    782      * @param windowId The window id.
    783      * @return Whether the window is present and its frame statistics
    784      *         were cleared.
    785      *
    786      * @see android.view.WindowContentFrameStats
    787      * @see #getWindowContentFrameStats(int)
    788      * @see #getWindows()
    789      * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
    790      */
    791     public boolean clearWindowContentFrameStats(int windowId) {
    792         synchronized (mLock) {
    793             throwIfNotConnectedLocked();
    794         }
    795         try {
    796             if (DEBUG) {
    797                 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
    798             }
    799             // Calling out without a lock held.
    800             return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
    801         } catch (RemoteException re) {
    802             Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
    803         }
    804         return false;
    805     }
    806 
    807     /**
    808      * Gets the frame statistics for a given window. These statistics contain
    809      * information about the most recently rendered content frames.
    810      * <p>
    811      * A typical usage requires clearing the window frame statistics via {@link
    812      * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
    813      * finally getting the window frame statistics via calling this method.
    814      * </p>
    815      * <pre>
    816      * // Assume we have at least one window.
    817      * final int windowId = getWindows().get(0).getId();
    818      *
    819      * // Start with a clean slate.
    820      * uiAutimation.clearWindowContentFrameStats(windowId);
    821      *
    822      * // Do stuff with the UI.
    823      *
    824      * // Get the frame statistics.
    825      * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
    826      * </pre>
    827      *
    828      * @param windowId The window id.
    829      * @return The window frame statistics, or null if the window is not present.
    830      *
    831      * @see android.view.WindowContentFrameStats
    832      * @see #clearWindowContentFrameStats(int)
    833      * @see #getWindows()
    834      * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
    835      */
    836     public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
    837         synchronized (mLock) {
    838             throwIfNotConnectedLocked();
    839         }
    840         try {
    841             if (DEBUG) {
    842                 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
    843             }
    844             // Calling out without a lock held.
    845             return mUiAutomationConnection.getWindowContentFrameStats(windowId);
    846         } catch (RemoteException re) {
    847             Log.e(LOG_TAG, "Error getting window content frame stats!", re);
    848         }
    849         return null;
    850     }
    851 
    852     /**
    853      * Clears the window animation rendering statistics. These statistics contain
    854      * information about the most recently rendered window animation frames, i.e.
    855      * for window transition animations.
    856      *
    857      * @see android.view.WindowAnimationFrameStats
    858      * @see #getWindowAnimationFrameStats()
    859      * @see android.R.styleable#WindowAnimation
    860      */
    861     public void clearWindowAnimationFrameStats() {
    862         synchronized (mLock) {
    863             throwIfNotConnectedLocked();
    864         }
    865         try {
    866             if (DEBUG) {
    867                 Log.i(LOG_TAG, "Clearing window animation frame stats");
    868             }
    869             // Calling out without a lock held.
    870             mUiAutomationConnection.clearWindowAnimationFrameStats();
    871         } catch (RemoteException re) {
    872             Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
    873         }
    874     }
    875 
    876     /**
    877      * Gets the window animation frame statistics. These statistics contain
    878      * information about the most recently rendered window animation frames, i.e.
    879      * for window transition animations.
    880      *
    881      * <p>
    882      * A typical usage requires clearing the window animation frame statistics via
    883      * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
    884      * a window transition which uses a window animation and finally getting the window
    885      * animation frame statistics by calling this method.
    886      * </p>
    887      * <pre>
    888      * // Start with a clean slate.
    889      * uiAutimation.clearWindowAnimationFrameStats();
    890      *
    891      * // Do stuff to trigger a window transition.
    892      *
    893      * // Get the frame statistics.
    894      * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
    895      * </pre>
    896      *
    897      * @return The window animation frame statistics.
    898      *
    899      * @see android.view.WindowAnimationFrameStats
    900      * @see #clearWindowAnimationFrameStats()
    901      * @see android.R.styleable#WindowAnimation
    902      */
    903     public WindowAnimationFrameStats getWindowAnimationFrameStats() {
    904         synchronized (mLock) {
    905             throwIfNotConnectedLocked();
    906         }
    907         try {
    908             if (DEBUG) {
    909                 Log.i(LOG_TAG, "Getting window animation frame stats");
    910             }
    911             // Calling out without a lock held.
    912             return mUiAutomationConnection.getWindowAnimationFrameStats();
    913         } catch (RemoteException re) {
    914             Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
    915         }
    916         return null;
    917     }
    918 
    919     /**
    920      * Grants a runtime permission to a package for a user.
    921      * @param packageName The package to which to grant.
    922      * @param permission The permission to grant.
    923      * @return Whether granting succeeded.
    924      *
    925      * @hide
    926      */
    927     @TestApi
    928     public boolean grantRuntimePermission(String packageName, String permission,
    929             UserHandle userHandle) {
    930         synchronized (mLock) {
    931             throwIfNotConnectedLocked();
    932         }
    933         try {
    934             if (DEBUG) {
    935                 Log.i(LOG_TAG, "Granting runtime permission");
    936             }
    937             // Calling out without a lock held.
    938             mUiAutomationConnection.grantRuntimePermission(packageName,
    939                     permission, userHandle.getIdentifier());
    940             // TODO: The package manager API should return boolean.
    941             return true;
    942         } catch (RemoteException re) {
    943             Log.e(LOG_TAG, "Error granting runtime permission", re);
    944         }
    945         return false;
    946     }
    947 
    948     /**
    949      * Revokes a runtime permission from a package for a user.
    950      * @param packageName The package from which to revoke.
    951      * @param permission The permission to revoke.
    952      * @return Whether revoking succeeded.
    953      *
    954      * @hide
    955      */
    956     @TestApi
    957     public boolean revokeRuntimePermission(String packageName, String permission,
    958             UserHandle userHandle) {
    959         synchronized (mLock) {
    960             throwIfNotConnectedLocked();
    961         }
    962         try {
    963             if (DEBUG) {
    964                 Log.i(LOG_TAG, "Revoking runtime permission");
    965             }
    966             // Calling out without a lock held.
    967             mUiAutomationConnection.revokeRuntimePermission(packageName,
    968                     permission, userHandle.getIdentifier());
    969             // TODO: The package manager API should return boolean.
    970             return true;
    971         } catch (RemoteException re) {
    972             Log.e(LOG_TAG, "Error revoking runtime permission", re);
    973         }
    974         return false;
    975     }
    976 
    977     /**
    978      * Executes a shell command. This method returns a file descriptor that points
    979      * to the standard output stream. The command execution is similar to running
    980      * "adb shell <command>" from a host connected to the device.
    981      * <p>
    982      * <strong>Note:</strong> It is your responsibility to close the returned file
    983      * descriptor once you are done reading.
    984      * </p>
    985      *
    986      * @param command The command to execute.
    987      * @return A file descriptor to the standard output stream.
    988      */
    989     public ParcelFileDescriptor executeShellCommand(String command) {
    990         synchronized (mLock) {
    991             throwIfNotConnectedLocked();
    992         }
    993 
    994         ParcelFileDescriptor source = null;
    995         ParcelFileDescriptor sink = null;
    996 
    997         try {
    998             ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
    999             source = pipe[0];
   1000             sink = pipe[1];
   1001 
   1002             // Calling out without a lock held.
   1003             mUiAutomationConnection.executeShellCommand(command, sink, null);
   1004         } catch (IOException ioe) {
   1005             Log.e(LOG_TAG, "Error executing shell command!", ioe);
   1006         } catch (RemoteException re) {
   1007             Log.e(LOG_TAG, "Error executing shell command!", re);
   1008         } finally {
   1009             IoUtils.closeQuietly(sink);
   1010         }
   1011 
   1012         return source;
   1013     }
   1014 
   1015     /**
   1016      * Executes a shell command. This method returns two file descriptors,
   1017      * one that points to the standard output stream (element at index 0), and one that points
   1018      * to the standard input stream (element at index 1). The command execution is similar
   1019      * to running "adb shell <command>" from a host connected to the device.
   1020      * <p>
   1021      * <strong>Note:</strong> It is your responsibility to close the returned file
   1022      * descriptors once you are done reading/writing.
   1023      * </p>
   1024      *
   1025      * @param command The command to execute.
   1026      * @return File descriptors (out, in) to the standard output/input streams.
   1027      *
   1028      * @hide
   1029      */
   1030     @TestApi
   1031     public ParcelFileDescriptor[] executeShellCommandRw(String command) {
   1032         synchronized (mLock) {
   1033             throwIfNotConnectedLocked();
   1034         }
   1035 
   1036         ParcelFileDescriptor source_read = null;
   1037         ParcelFileDescriptor sink_read = null;
   1038 
   1039         ParcelFileDescriptor source_write = null;
   1040         ParcelFileDescriptor sink_write = null;
   1041 
   1042         try {
   1043             ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe();
   1044             source_read = pipe_read[0];
   1045             sink_read = pipe_read[1];
   1046 
   1047             ParcelFileDescriptor[] pipe_write = ParcelFileDescriptor.createPipe();
   1048             source_write = pipe_write[0];
   1049             sink_write = pipe_write[1];
   1050 
   1051             // Calling out without a lock held.
   1052             mUiAutomationConnection.executeShellCommand(command, sink_read, source_write);
   1053         } catch (IOException ioe) {
   1054             Log.e(LOG_TAG, "Error executing shell command!", ioe);
   1055         } catch (RemoteException re) {
   1056             Log.e(LOG_TAG, "Error executing shell command!", re);
   1057         } finally {
   1058             IoUtils.closeQuietly(sink_read);
   1059             IoUtils.closeQuietly(source_write);
   1060         }
   1061 
   1062         ParcelFileDescriptor[] result = new ParcelFileDescriptor[2];
   1063         result[0] = source_read;
   1064         result[1] = sink_write;
   1065         return result;
   1066     }
   1067 
   1068     private static float getDegreesForRotation(int value) {
   1069         switch (value) {
   1070             case Surface.ROTATION_90: {
   1071                 return 360f - 90f;
   1072             }
   1073             case Surface.ROTATION_180: {
   1074                 return 360f - 180f;
   1075             }
   1076             case Surface.ROTATION_270: {
   1077                 return 360f - 270f;
   1078             } default: {
   1079                 return 0;
   1080             }
   1081         }
   1082     }
   1083 
   1084     private boolean isConnectedLocked() {
   1085         return mConnectionId != CONNECTION_ID_UNDEFINED;
   1086     }
   1087 
   1088     private void throwIfConnectedLocked() {
   1089         if (mConnectionId != CONNECTION_ID_UNDEFINED) {
   1090             throw new IllegalStateException("UiAutomation not connected!");
   1091         }
   1092     }
   1093 
   1094     private void throwIfNotConnectedLocked() {
   1095         if (!isConnectedLocked()) {
   1096             throw new IllegalStateException("UiAutomation not connected!");
   1097         }
   1098     }
   1099 
   1100     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
   1101 
   1102         public IAccessibilityServiceClientImpl(Looper looper) {
   1103             super(null, looper, new Callbacks() {
   1104                 @Override
   1105                 public void init(int connectionId, IBinder windowToken) {
   1106                     synchronized (mLock) {
   1107                         mConnectionId = connectionId;
   1108                         mLock.notifyAll();
   1109                     }
   1110                 }
   1111 
   1112                 @Override
   1113                 public void onServiceConnected() {
   1114                     /* do nothing */
   1115                 }
   1116 
   1117                 @Override
   1118                 public void onInterrupt() {
   1119                     /* do nothing */
   1120                 }
   1121 
   1122                 @Override
   1123                 public boolean onGesture(int gestureId) {
   1124                     /* do nothing */
   1125                     return false;
   1126                 }
   1127 
   1128                 @Override
   1129                 public void onAccessibilityEvent(AccessibilityEvent event) {
   1130                     synchronized (mLock) {
   1131                         mLastEventTimeMillis = event.getEventTime();
   1132                         if (mWaitingForEventDelivery) {
   1133                             mEventQueue.add(AccessibilityEvent.obtain(event));
   1134                         }
   1135                         mLock.notifyAll();
   1136                     }
   1137                     // Calling out only without a lock held.
   1138                     final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
   1139                     if (listener != null) {
   1140                         listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
   1141                     }
   1142                 }
   1143 
   1144                 @Override
   1145                 public boolean onKeyEvent(KeyEvent event) {
   1146                     return false;
   1147                 }
   1148 
   1149                 @Override
   1150                 public void onMagnificationChanged(@NonNull Region region,
   1151                         float scale, float centerX, float centerY) {
   1152                     /* do nothing */
   1153                 }
   1154 
   1155                 @Override
   1156                 public void onSoftKeyboardShowModeChanged(int showMode) {
   1157                     /* do nothing */
   1158                 }
   1159 
   1160                 @Override
   1161                 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
   1162                     /* do nothing */
   1163                 }
   1164 
   1165                 @Override
   1166                 public void onFingerprintCapturingGesturesChanged(boolean active) {
   1167                     /* do nothing */
   1168                 }
   1169 
   1170                 @Override
   1171                 public void onFingerprintGesture(int gesture) {
   1172                     /* do nothing */
   1173                 }
   1174 
   1175                 @Override
   1176                 public void onAccessibilityButtonClicked() {
   1177                     /* do nothing */
   1178                 }
   1179 
   1180                 @Override
   1181                 public void onAccessibilityButtonAvailabilityChanged(boolean available) {
   1182                     /* do nothing */
   1183                 }
   1184             });
   1185         }
   1186     }
   1187 }
   1188