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