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