Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import android.accessibilityservice.AccessibilityService.Callbacks;
     20 import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
     21 import android.accessibilityservice.AccessibilityServiceInfo;
     22 import android.accessibilityservice.IAccessibilityServiceClient;
     23 import android.accessibilityservice.IAccessibilityServiceConnection;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Canvas;
     26 import android.graphics.Point;
     27 import android.hardware.display.DisplayManagerGlobal;
     28 import android.os.Looper;
     29 import android.os.RemoteException;
     30 import android.os.SystemClock;
     31 import android.util.Log;
     32 import android.view.Display;
     33 import android.view.InputEvent;
     34 import android.view.KeyEvent;
     35 import android.view.Surface;
     36 import android.view.accessibility.AccessibilityEvent;
     37 import android.view.accessibility.AccessibilityInteractionClient;
     38 import android.view.accessibility.AccessibilityNodeInfo;
     39 import android.view.accessibility.IAccessibilityInteractionConnection;
     40 
     41 import java.util.ArrayList;
     42 import java.util.concurrent.TimeoutException;
     43 
     44 /**
     45  * Class for interacting with the device's UI by simulation user actions and
     46  * introspection of the screen content. It relies on the platform accessibility
     47  * APIs to introspect the screen and to perform some actions on the remote view
     48  * tree. It also allows injecting of arbitrary raw input events simulating user
     49  * interaction with keyboards and touch devices. One can think of a UiAutomation
     50  * as a special type of {@link android.accessibilityservice.AccessibilityService}
     51  * which does not provide hooks for the service life cycle and exposes other
     52  * APIs that are useful for UI test automation.
     53  * <p>
     54  * The APIs exposed by this class are low-level to maximize flexibility when
     55  * developing UI test automation tools and libraries. Generally, a UiAutomation
     56  * client should be using a higher-level library or implement high-level functions.
     57  * For example, performing a tap on the screen requires construction and injecting
     58  * of a touch down and up events which have to be delivered to the system by a
     59  * call to {@link #injectInputEvent(InputEvent, boolean)}.
     60  * </p>
     61  * <p>
     62  * The APIs exposed by this class operate across applications enabling a client
     63  * to write tests that cover use cases spanning over multiple applications. For
     64  * example, going to the settings application to change a setting and then
     65  * interacting with another application whose behavior depends on that setting.
     66  * </p>
     67  */
     68 public final class UiAutomation {
     69 
     70     private static final String LOG_TAG = UiAutomation.class.getSimpleName();
     71 
     72     private static final boolean DEBUG = false;
     73 
     74     private static final int CONNECTION_ID_UNDEFINED = -1;
     75 
     76     private static final long CONNECT_TIMEOUT_MILLIS = 5000;
     77 
     78     /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
     79     public static final int ROTATION_UNFREEZE = -2;
     80 
     81     /** Rotation constant: Freeze rotation to its current state. */
     82     public static final int ROTATION_FREEZE_CURRENT = -1;
     83 
     84     /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
     85     public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
     86 
     87     /** Rotation constant: Freeze rotation to 90 degrees . */
     88     public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
     89 
     90     /** Rotation constant: Freeze rotation to 180 degrees . */
     91     public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
     92 
     93     /** Rotation constant: Freeze rotation to 270 degrees . */
     94     public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
     95 
     96     private final Object mLock = new Object();
     97 
     98     private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
     99 
    100     private final IAccessibilityServiceClient mClient;
    101 
    102     private final IUiAutomationConnection mUiAutomationConnection;
    103 
    104     private int mConnectionId = CONNECTION_ID_UNDEFINED;
    105 
    106     private OnAccessibilityEventListener mOnAccessibilityEventListener;
    107 
    108     private boolean mWaitingForEventDelivery;
    109 
    110     private long mLastEventTimeMillis;
    111 
    112     private boolean mIsConnecting;
    113 
    114     /**
    115      * Listener for observing the {@link AccessibilityEvent} stream.
    116      */
    117     public static interface OnAccessibilityEventListener {
    118 
    119         /**
    120          * Callback for receiving an {@link AccessibilityEvent}.
    121          * <p>
    122          * <strong>Note:</strong> This method is <strong>NOT</strong> executed
    123          * on the main test thread. The client is responsible for proper
    124          * synchronization.
    125          * </p>
    126          * <p>
    127          * <strong>Note:</strong> It is responsibility of the client
    128          * to recycle the received events to minimize object creation.
    129          * </p>
    130          *
    131          * @param event The received event.
    132          */
    133         public void onAccessibilityEvent(AccessibilityEvent event);
    134     }
    135 
    136     /**
    137      * Listener for filtering accessibility events.
    138      */
    139     public static interface AccessibilityEventFilter {
    140 
    141         /**
    142          * Callback for determining whether an event is accepted or
    143          * it is filtered out.
    144          *
    145          * @param event The event to process.
    146          * @return True if the event is accepted, false to filter it out.
    147          */
    148         public boolean accept(AccessibilityEvent event);
    149     }
    150 
    151     /**
    152      * Creates a new instance that will handle callbacks from the accessibility
    153      * layer on the thread of the provided looper and perform requests for privileged
    154      * operations on the provided connection.
    155      *
    156      * @param looper The looper on which to execute accessibility callbacks.
    157      * @param connection The connection for performing privileged operations.
    158      *
    159      * @hide
    160      */
    161     public UiAutomation(Looper looper, IUiAutomationConnection connection) {
    162         if (looper == null) {
    163             throw new IllegalArgumentException("Looper cannot be null!");
    164         }
    165         if (connection == null) {
    166             throw new IllegalArgumentException("Connection cannot be null!");
    167         }
    168         mUiAutomationConnection = connection;
    169         mClient = new IAccessibilityServiceClientImpl(looper);
    170     }
    171 
    172     /**
    173      * Connects this UiAutomation to the accessibility introspection APIs.
    174      *
    175      * @hide
    176      */
    177     public void connect() {
    178         synchronized (mLock) {
    179             throwIfConnectedLocked();
    180             if (mIsConnecting) {
    181                 return;
    182             }
    183             mIsConnecting = true;
    184         }
    185 
    186         try {
    187             // Calling out without a lock held.
    188             mUiAutomationConnection.connect(mClient);
    189         } catch (RemoteException re) {
    190             throw new RuntimeException("Error while connecting UiAutomation", re);
    191         }
    192 
    193         synchronized (mLock) {
    194             final long startTimeMillis = SystemClock.uptimeMillis();
    195             try {
    196                 while (true) {
    197                     if (isConnectedLocked()) {
    198                         break;
    199                     }
    200                     final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
    201                     final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
    202                     if (remainingTimeMillis <= 0) {
    203                         throw new RuntimeException("Error while connecting UiAutomation");
    204                     }
    205                     try {
    206                         mLock.wait(remainingTimeMillis);
    207                     } catch (InterruptedException ie) {
    208                         /* ignore */
    209                     }
    210                 }
    211             } finally {
    212                 mIsConnecting = false;
    213             }
    214         }
    215     }
    216 
    217     /**
    218      * Disconnects this UiAutomation from the accessibility introspection APIs.
    219      *
    220      * @hide
    221      */
    222     public void disconnect() {
    223         synchronized (mLock) {
    224             if (mIsConnecting) {
    225                 throw new IllegalStateException(
    226                         "Cannot call disconnect() while connecting!");
    227             }
    228             throwIfNotConnectedLocked();
    229             mConnectionId = CONNECTION_ID_UNDEFINED;
    230         }
    231         try {
    232             // Calling out without a lock held.
    233             mUiAutomationConnection.disconnect();
    234         } catch (RemoteException re) {
    235             throw new RuntimeException("Error while disconnecting UiAutomation", re);
    236         }
    237     }
    238 
    239     /**
    240      * The id of the {@link IAccessibilityInteractionConnection} for querying
    241      * the screen content. This is here for legacy purposes since some tools use
    242      * hidden APIs to introspect the screen.
    243      *
    244      * @hide
    245      */
    246     public int getConnectionId() {
    247         synchronized (mLock) {
    248             throwIfNotConnectedLocked();
    249             return mConnectionId;
    250         }
    251     }
    252 
    253     /**
    254      * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
    255      *
    256      * @param listener The callback.
    257      */
    258     public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
    259         synchronized (mLock) {
    260             mOnAccessibilityEventListener = listener;
    261         }
    262     }
    263 
    264     /**
    265      * Performs a global action. Such an action can be performed at any moment
    266      * regardless of the current application or user location in that application.
    267      * For example going back, going home, opening recents, etc.
    268      *
    269      * @param action The action to perform.
    270      * @return Whether the action was successfully performed.
    271      *
    272      * @see AccessibilityService#GLOBAL_ACTION_BACK
    273      * @see AccessibilityService#GLOBAL_ACTION_HOME
    274      * @see AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
    275      * @see AccessibilityService#GLOBAL_ACTION_RECENTS
    276      */
    277     public final boolean performGlobalAction(int action) {
    278         final IAccessibilityServiceConnection connection;
    279         synchronized (mLock) {
    280             throwIfNotConnectedLocked();
    281             connection = AccessibilityInteractionClient.getInstance()
    282                     .getConnection(mConnectionId);
    283         }
    284         // Calling out without a lock held.
    285         if (connection != null) {
    286             try {
    287                 return connection.performGlobalAction(action);
    288             } catch (RemoteException re) {
    289                 Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
    290             }
    291         }
    292         return false;
    293     }
    294 
    295     /**
    296      * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
    297      * This method is useful if one wants to change some of the dynamically
    298      * configurable properties at runtime.
    299      *
    300      * @return The accessibility service info.
    301      *
    302      * @see AccessibilityServiceInfo
    303      */
    304     public final AccessibilityServiceInfo getServiceInfo() {
    305         final IAccessibilityServiceConnection connection;
    306         synchronized (mLock) {
    307             throwIfNotConnectedLocked();
    308             connection = AccessibilityInteractionClient.getInstance()
    309                     .getConnection(mConnectionId);
    310         }
    311         // Calling out without a lock held.
    312         if (connection != null) {
    313             try {
    314                 return connection.getServiceInfo();
    315             } catch (RemoteException re) {
    316                 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
    317             }
    318         }
    319         return null;
    320     }
    321 
    322     /**
    323      * Sets the {@link AccessibilityServiceInfo} that describes how this
    324      * UiAutomation will be handled by the platform accessibility layer.
    325      *
    326      * @param info The info.
    327      *
    328      * @see AccessibilityServiceInfo
    329      */
    330     public final void setServiceInfo(AccessibilityServiceInfo info) {
    331         final IAccessibilityServiceConnection connection;
    332         synchronized (mLock) {
    333             throwIfNotConnectedLocked();
    334             AccessibilityInteractionClient.getInstance().clearCache();
    335             connection = AccessibilityInteractionClient.getInstance()
    336                     .getConnection(mConnectionId);
    337         }
    338         // Calling out without a lock held.
    339         if (connection != null) {
    340             try {
    341                 connection.setServiceInfo(info);
    342             } catch (RemoteException re) {
    343                 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
    344             }
    345         }
    346     }
    347 
    348     /**
    349      * Gets the root {@link AccessibilityNodeInfo} in the active window.
    350      *
    351      * @return The root info.
    352      */
    353     public AccessibilityNodeInfo getRootInActiveWindow() {
    354         final int connectionId;
    355         synchronized (mLock) {
    356             throwIfNotConnectedLocked();
    357             connectionId = mConnectionId;
    358         }
    359         // Calling out without a lock held.
    360         return AccessibilityInteractionClient.getInstance()
    361                 .getRootInActiveWindow(connectionId);
    362     }
    363 
    364     /**
    365      * A method for injecting an arbitrary input event.
    366      * <p>
    367      * <strong>Note:</strong> It is caller's responsibility to recycle the event.
    368      * </p>
    369      * @param event The event to inject.
    370      * @param sync Whether to inject the event synchronously.
    371      * @return Whether event injection succeeded.
    372      */
    373     public boolean injectInputEvent(InputEvent event, boolean sync) {
    374         synchronized (mLock) {
    375             throwIfNotConnectedLocked();
    376         }
    377         try {
    378             if (DEBUG) {
    379                 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
    380             }
    381             // Calling out without a lock held.
    382             return mUiAutomationConnection.injectInputEvent(event, sync);
    383         } catch (RemoteException re) {
    384             Log.e(LOG_TAG, "Error while injecting input event!", re);
    385         }
    386         return false;
    387     }
    388 
    389     /**
    390      * Sets the device rotation. A client can freeze the rotation in
    391      * desired state or freeze the rotation to its current state or
    392      * unfreeze the rotation (rotating the device changes its rotation
    393      * state).
    394      *
    395      * @param rotation The desired rotation.
    396      * @return Whether the rotation was set successfully.
    397      *
    398      * @see #ROTATION_FREEZE_0
    399      * @see #ROTATION_FREEZE_90
    400      * @see #ROTATION_FREEZE_180
    401      * @see #ROTATION_FREEZE_270
    402      * @see #ROTATION_FREEZE_CURRENT
    403      * @see #ROTATION_UNFREEZE
    404      */
    405     public boolean setRotation(int rotation) {
    406         synchronized (mLock) {
    407             throwIfNotConnectedLocked();
    408         }
    409         switch (rotation) {
    410             case ROTATION_FREEZE_0:
    411             case ROTATION_FREEZE_90:
    412             case ROTATION_FREEZE_180:
    413             case ROTATION_FREEZE_270:
    414             case ROTATION_UNFREEZE:
    415             case ROTATION_FREEZE_CURRENT: {
    416                 try {
    417                     // Calling out without a lock held.
    418                     mUiAutomationConnection.setRotation(rotation);
    419                     return true;
    420                 } catch (RemoteException re) {
    421                     Log.e(LOG_TAG, "Error while setting rotation!", re);
    422                 }
    423             } return false;
    424             default: {
    425                 throw new IllegalArgumentException("Invalid rotation.");
    426             }
    427         }
    428     }
    429 
    430     /**
    431      * Executes a command and waits for a specific accessibility event up to a
    432      * given wait timeout. To detect a sequence of events one can implement a
    433      * filter that keeps track of seen events of the expected sequence and
    434      * returns true after the last event of that sequence is received.
    435      * <p>
    436      * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
    437      * </p>
    438      * @param command The command to execute.
    439      * @param filter Filter that recognizes the expected event.
    440      * @param timeoutMillis The wait timeout in milliseconds.
    441      *
    442      * @throws TimeoutException If the expected event is not received within the timeout.
    443      */
    444     public AccessibilityEvent executeAndWaitForEvent(Runnable command,
    445             AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
    446         // Acquire the lock and prepare for receiving events.
    447         synchronized (mLock) {
    448             throwIfNotConnectedLocked();
    449             mEventQueue.clear();
    450             // Prepare to wait for an event.
    451             mWaitingForEventDelivery = true;
    452         }
    453 
    454         // Note: We have to release the lock since calling out with this lock held
    455         // can bite. We will correctly filter out events from other interactions,
    456         // so starting to collect events before running the action is just fine.
    457 
    458         // We will ignore events from previous interactions.
    459         final long executionStartTimeMillis = SystemClock.uptimeMillis();
    460         // Execute the command *without* the lock being held.
    461         command.run();
    462 
    463         // Acquire the lock and wait for the event.
    464         synchronized (mLock) {
    465             try {
    466                 // Wait for the event.
    467                 final long startTimeMillis = SystemClock.uptimeMillis();
    468                 while (true) {
    469                     // Drain the event queue
    470                     while (!mEventQueue.isEmpty()) {
    471                         AccessibilityEvent event = mEventQueue.remove(0);
    472                         // Ignore events from previous interactions.
    473                         if (event.getEventTime() < executionStartTimeMillis) {
    474                             continue;
    475                         }
    476                         if (filter.accept(event)) {
    477                             return event;
    478                         }
    479                         event.recycle();
    480                     }
    481                     // Check if timed out and if not wait.
    482                     final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
    483                     final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
    484                     if (remainingTimeMillis <= 0) {
    485                         throw new TimeoutException("Expected event not received within: "
    486                                 + timeoutMillis + " ms.");
    487                     }
    488                     try {
    489                         mLock.wait(remainingTimeMillis);
    490                     } catch (InterruptedException ie) {
    491                         /* ignore */
    492                     }
    493                 }
    494             } finally {
    495                 mWaitingForEventDelivery = false;
    496                 mEventQueue.clear();
    497                 mLock.notifyAll();
    498             }
    499         }
    500     }
    501 
    502     /**
    503      * Waits for the accessibility event stream to become idle, which is not to
    504      * have received an accessibility event within <code>idleTimeoutMillis</code>.
    505      * The total time spent to wait for an idle accessibility event stream is bounded
    506      * by the <code>globalTimeoutMillis</code>.
    507      *
    508      * @param idleTimeoutMillis The timeout in milliseconds between two events
    509      *            to consider the device idle.
    510      * @param globalTimeoutMillis The maximal global timeout in milliseconds in
    511      *            which to wait for an idle state.
    512      *
    513      * @throws TimeoutException If no idle state was detected within
    514      *            <code>globalTimeoutMillis.</code>
    515      */
    516     public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
    517             throws TimeoutException {
    518         synchronized (mLock) {
    519             throwIfNotConnectedLocked();
    520 
    521             final long startTimeMillis = SystemClock.uptimeMillis();
    522             if (mLastEventTimeMillis <= 0) {
    523                 mLastEventTimeMillis = startTimeMillis;
    524             }
    525 
    526             while (true) {
    527                 final long currentTimeMillis = SystemClock.uptimeMillis();
    528                 // Did we get idle state within the global timeout?
    529                 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
    530                 final long remainingGlobalTimeMillis =
    531                         globalTimeoutMillis - elapsedGlobalTimeMillis;
    532                 if (remainingGlobalTimeMillis <= 0) {
    533                     throw new TimeoutException("No idle state with idle timeout: "
    534                             + idleTimeoutMillis + " within global timeout: "
    535                             + globalTimeoutMillis);
    536                 }
    537                 // Did we get an idle state within the idle timeout?
    538                 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
    539                 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
    540                 if (remainingIdleTimeMillis <= 0) {
    541                     return;
    542                 }
    543                 try {
    544                      mLock.wait(remainingIdleTimeMillis);
    545                 } catch (InterruptedException ie) {
    546                      /* ignore */
    547                 }
    548             }
    549         }
    550     }
    551 
    552     /**
    553      * Takes a screenshot.
    554      *
    555      * @return The screenshot bitmap on success, null otherwise.
    556      */
    557     public Bitmap takeScreenshot() {
    558         synchronized (mLock) {
    559             throwIfNotConnectedLocked();
    560         }
    561         Display display = DisplayManagerGlobal.getInstance()
    562                 .getRealDisplay(Display.DEFAULT_DISPLAY);
    563         Point displaySize = new Point();
    564         display.getRealSize(displaySize);
    565         final int displayWidth = displaySize.x;
    566         final int displayHeight = displaySize.y;
    567 
    568         final float screenshotWidth;
    569         final float screenshotHeight;
    570 
    571         final int rotation = display.getRotation();
    572         switch (rotation) {
    573             case ROTATION_FREEZE_0: {
    574                 screenshotWidth = displayWidth;
    575                 screenshotHeight = displayHeight;
    576             } break;
    577             case ROTATION_FREEZE_90: {
    578                 screenshotWidth = displayHeight;
    579                 screenshotHeight = displayWidth;
    580             } break;
    581             case ROTATION_FREEZE_180: {
    582                 screenshotWidth = displayWidth;
    583                 screenshotHeight = displayHeight;
    584             } break;
    585             case ROTATION_FREEZE_270: {
    586                 screenshotWidth = displayHeight;
    587                 screenshotHeight = displayWidth;
    588             } break;
    589             default: {
    590                 throw new IllegalArgumentException("Invalid rotation: "
    591                         + rotation);
    592             }
    593         }
    594 
    595         // Take the screenshot
    596         Bitmap screenShot = null;
    597         try {
    598             // Calling out without a lock held.
    599             screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
    600                     (int) screenshotHeight);
    601             if (screenShot == null) {
    602                 return null;
    603             }
    604         } catch (RemoteException re) {
    605             Log.e(LOG_TAG, "Error while taking screnshot!", re);
    606             return null;
    607         }
    608 
    609         // Rotate the screenshot to the current orientation
    610         if (rotation != ROTATION_FREEZE_0) {
    611             Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
    612                     Bitmap.Config.ARGB_8888);
    613             Canvas canvas = new Canvas(unrotatedScreenShot);
    614             canvas.translate(unrotatedScreenShot.getWidth() / 2,
    615                     unrotatedScreenShot.getHeight() / 2);
    616             canvas.rotate(getDegreesForRotation(rotation));
    617             canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
    618             canvas.drawBitmap(screenShot, 0, 0, null);
    619             canvas.setBitmap(null);
    620             screenShot = unrotatedScreenShot;
    621         }
    622 
    623         // Optimization
    624         screenShot.setHasAlpha(false);
    625 
    626         return screenShot;
    627     }
    628 
    629     /**
    630      * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
    631      * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
    632      * potentially undesirable actions such as calling 911 or posting on public forums etc.
    633      *
    634      * @param enable whether to run in a "monkey" mode or not. Default is not.
    635      * @see {@link ActivityManager#isUserAMonkey()}
    636      */
    637     public void setRunAsMonkey(boolean enable) {
    638         synchronized (mLock) {
    639             throwIfNotConnectedLocked();
    640         }
    641         try {
    642             ActivityManagerNative.getDefault().setUserIsMonkey(enable);
    643         } catch (RemoteException re) {
    644             Log.e(LOG_TAG, "Error while setting run as monkey!", re);
    645         }
    646     }
    647 
    648     private static float getDegreesForRotation(int value) {
    649         switch (value) {
    650             case Surface.ROTATION_90: {
    651                 return 360f - 90f;
    652             }
    653             case Surface.ROTATION_180: {
    654                 return 360f - 180f;
    655             }
    656             case Surface.ROTATION_270: {
    657                 return 360f - 270f;
    658             } default: {
    659                 return 0;
    660             }
    661         }
    662     }
    663 
    664     private boolean isConnectedLocked() {
    665         return mConnectionId != CONNECTION_ID_UNDEFINED;
    666     }
    667 
    668     private void throwIfConnectedLocked() {
    669         if (mConnectionId != CONNECTION_ID_UNDEFINED) {
    670             throw new IllegalStateException("UiAutomation not connected!");
    671         }
    672     }
    673 
    674     private void throwIfNotConnectedLocked() {
    675         if (!isConnectedLocked()) {
    676             throw new IllegalStateException("UiAutomation not connected!");
    677         }
    678     }
    679 
    680     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
    681 
    682         public IAccessibilityServiceClientImpl(Looper looper) {
    683             super(null, looper, new Callbacks() {
    684                 @Override
    685                 public void onSetConnectionId(int connectionId) {
    686                     synchronized (mLock) {
    687                         mConnectionId = connectionId;
    688                         mLock.notifyAll();
    689                     }
    690                 }
    691 
    692                 @Override
    693                 public void onServiceConnected() {
    694                     /* do nothing */
    695                 }
    696 
    697                 @Override
    698                 public void onInterrupt() {
    699                     /* do nothing */
    700                 }
    701 
    702                 @Override
    703                 public boolean onGesture(int gestureId) {
    704                     /* do nothing */
    705                     return false;
    706                 }
    707 
    708                 @Override
    709                 public void onAccessibilityEvent(AccessibilityEvent event) {
    710                     synchronized (mLock) {
    711                         mLastEventTimeMillis = event.getEventTime();
    712                         if (mWaitingForEventDelivery) {
    713                             mEventQueue.add(AccessibilityEvent.obtain(event));
    714                         }
    715                         mLock.notifyAll();
    716                     }
    717                     // Calling out only without a lock held.
    718                     final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
    719                     if (listener != null) {
    720                         listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
    721                     }
    722                 }
    723 
    724                 @Override
    725                 public boolean onKeyEvent(KeyEvent event) {
    726                     return false;
    727                 }
    728             });
    729         }
    730     }
    731 }
    732