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