• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onAccessibilityEvent(AccessibilityEvent event)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          */
accept(AccessibilityEvent event)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      */
UiAutomation(Looper looper, IUiAutomationConnection connection)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      */
connect()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      */
disconnect()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      */
getConnectionId()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      */
setOnAccessibilityEventListener(OnAccessibilityEventListener listener)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      */
performGlobalAction(int action)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      */
getServiceInfo()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      */
setServiceInfo(AccessibilityServiceInfo info)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      */
getRootInActiveWindow()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      */
injectInputEvent(InputEvent event, boolean sync)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      */
setRotation(int rotation)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      */
executeAndWaitForEvent(Runnable command, AccessibilityEventFilter filter, long timeoutMillis)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      */
waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)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      */
takeScreenshot()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      */
setRunAsMonkey(boolean enable)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 
getDegreesForRotation(int value)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 
isConnectedLocked()664     private boolean isConnectedLocked() {
665         return mConnectionId != CONNECTION_ID_UNDEFINED;
666     }
667 
throwIfConnectedLocked()668     private void throwIfConnectedLocked() {
669         if (mConnectionId != CONNECTION_ID_UNDEFINED) {
670             throw new IllegalStateException("UiAutomation not connected!");
671         }
672     }
673 
throwIfNotConnectedLocked()674     private void throwIfNotConnectedLocked() {
675         if (!isConnectedLocked()) {
676             throw new IllegalStateException("UiAutomation not connected!");
677         }
678     }
679 
680     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
681 
IAccessibilityServiceClientImpl(Looper looper)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