AccessibilityService.java revision 14ed6cf3e7bada4932314969000d384bed6d3f92
1/* 2 * Copyright (C) 2009 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 17package android.accessibilityservice; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.app.Service; 22import android.content.Context; 23import android.content.Intent; 24import android.graphics.Region; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.Looper; 28import android.os.Message; 29import android.os.RemoteException; 30import android.util.ArrayMap; 31import android.util.Log; 32import android.util.Slog; 33import android.view.KeyEvent; 34import android.view.WindowManager; 35import android.view.WindowManagerImpl; 36import android.view.accessibility.AccessibilityEvent; 37import android.view.accessibility.AccessibilityInteractionClient; 38import android.view.accessibility.AccessibilityNodeInfo; 39import android.view.accessibility.AccessibilityWindowInfo; 40 41import com.android.internal.os.HandlerCaller; 42import com.android.internal.os.SomeArgs; 43 44import java.util.ArrayList; 45import java.util.List; 46import java.util.Map.Entry; 47import java.util.Set; 48 49/** 50 * An accessibility service runs in the background and receives callbacks by the system 51 * when {@link AccessibilityEvent}s are fired. Such events denote some state transition 52 * in the user interface, for example, the focus has changed, a button has been clicked, 53 * etc. Such a service can optionally request the capability for querying the content 54 * of the active window. Development of an accessibility service requires extending this 55 * class and implementing its abstract methods. 56 * 57 * <div class="special reference"> 58 * <h3>Developer Guides</h3> 59 * <p>For more information about creating AccessibilityServices, read the 60 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a> 61 * developer guide.</p> 62 * </div> 63 * 64 * <h3>Lifecycle</h3> 65 * <p> 66 * The lifecycle of an accessibility service is managed exclusively by the system and 67 * follows the established service life cycle. Additionally, starting or stopping an 68 * accessibility service is triggered exclusively by an explicit user action through 69 * enabling or disabling it in the device settings. After the system binds to a service it 70 * calls {@link AccessibilityService#onServiceConnected()}. This method can be 71 * overriden by clients that want to perform post binding setup. 72 * </p> 73 * <h3>Declaration</h3> 74 * <p> 75 * An accessibility is declared as any other service in an AndroidManifest.xml but it 76 * must also specify that it handles the "android.accessibilityservice.AccessibilityService" 77 * {@link android.content.Intent}. Failure to declare this intent will cause the system to 78 * ignore the accessibility service. Additionally an accessibility service must request the 79 * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to ensure 80 * that only the system 81 * can bind to it. Failure to declare this intent will cause the system to ignore the 82 * accessibility service. Following is an example declaration: 83 * </p> 84 * <pre> <service android:name=".MyAccessibilityService" 85 * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> 86 * <intent-filter> 87 * <action android:name="android.accessibilityservice.AccessibilityService" /> 88 * </intent-filter> 89 * . . . 90 * </service></pre> 91 * <h3>Configuration</h3> 92 * <p> 93 * An accessibility service can be configured to receive specific types of accessibility events, 94 * listen only to specific packages, get events from each type only once in a given time frame, 95 * retrieve window content, specify a settings activity, etc. 96 * </p> 97 * <p> 98 * There are two approaches for configuring an accessibility service: 99 * </p> 100 * <ul> 101 * <li> 102 * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring 103 * the service. A service declaration with a meta-data tag is presented below: 104 * <pre> <service android:name=".MyAccessibilityService"> 105 * <intent-filter> 106 * <action android:name="android.accessibilityservice.AccessibilityService" /> 107 * </intent-filter> 108 * <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /> 109 * </service></pre> 110 * <p class="note"> 111 * <strong>Note:</strong> This approach enables setting all properties. 112 * </p> 113 * <p> 114 * For more details refer to {@link #SERVICE_META_DATA} and 115 * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code>. 116 * </p> 117 * </li> 118 * <li> 119 * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note 120 * that this method can be called any time to dynamically change the service configuration. 121 * <p class="note"> 122 * <strong>Note:</strong> This approach enables setting only dynamically configurable properties: 123 * {@link AccessibilityServiceInfo#eventTypes}, 124 * {@link AccessibilityServiceInfo#feedbackType}, 125 * {@link AccessibilityServiceInfo#flags}, 126 * {@link AccessibilityServiceInfo#notificationTimeout}, 127 * {@link AccessibilityServiceInfo#packageNames} 128 * </p> 129 * <p> 130 * For more details refer to {@link AccessibilityServiceInfo}. 131 * </p> 132 * </li> 133 * </ul> 134 * <h3>Retrieving window content</h3> 135 * <p> 136 * A service can specify in its declaration that it can retrieve the active window 137 * content which is represented as a tree of {@link AccessibilityNodeInfo}. Note that 138 * declaring this capability requires that the service declares its configuration via 139 * an XML resource referenced by {@link #SERVICE_META_DATA}. 140 * </p> 141 * <p> 142 * For security purposes an accessibility service can retrieve only the content of the 143 * currently active window. The currently active window is defined as the window from 144 * which was fired the last event of the following types: 145 * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}, 146 * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, 147 * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}, 148 * In other words, the last window that was shown or the last window that the user has touched 149 * during touch exploration. 150 * </p> 151 * <p> 152 * The entry point for retrieving window content is through calling 153 * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()} of the last received 154 * event of the above types or a previous event from the same window 155 * (see {@link AccessibilityEvent#getWindowId() AccessibilityEvent.getWindowId()}). Invoking 156 * this method will return an {@link AccessibilityNodeInfo} that can be used to traverse the 157 * window content which represented as a tree of such objects. 158 * </p> 159 * <p class="note"> 160 * <strong>Note</strong> An accessibility service may have requested to be notified for 161 * a subset of the event types, thus be unaware that the active window has changed. Therefore 162 * accessibility service that would like to retrieve window content should: 163 * <ul> 164 * <li> 165 * Register for all event types with no notification timeout and keep track for the active 166 * window by calling {@link AccessibilityEvent#getWindowId()} of the last received event and 167 * compare this with the {@link AccessibilityNodeInfo#getWindowId()} before calling retrieval 168 * methods on the latter. 169 * </li> 170 * <li> 171 * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail since the 172 * active window has changed and the service did not get the accessibility event yet. Note 173 * that it is possible to have a retrieval method failing even adopting the strategy 174 * specified in the previous bullet because the accessibility event dispatch is asynchronous 175 * and crosses process boundaries. 176 * </li> 177 * </ul> 178 * </p> 179 * <h3>Notification strategy</h3> 180 * <p> 181 * All accessibility services are notified of all events they have requested, regardless of their 182 * feedback type. 183 * </p> 184 * <p class="note"> 185 * <strong>Note:</strong> The event notification timeout is useful to avoid propagating 186 * events to the client too frequently since this is accomplished via an expensive 187 * interprocess call. One can think of the timeout as a criteria to determine when 188 * event generation has settled down.</p> 189 * <h3>Event types</h3> 190 * <ul> 191 * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li> 192 * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li> 193 * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li> 194 * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li> 195 * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li> 196 * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li> 197 * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li> 198 * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li> 199 * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li> 200 * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li> 201 * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li> 202 * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li> 203 * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li> 204 * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li> 205 * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li> 206 * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li> 207 * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li> 208 * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li> 209 * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li> 210 * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li> 211 * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li> 212 * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li> 213 * </ul> 214 * <h3>Feedback types</h3> 215 * <ul> 216 * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li> 217 * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li> 218 * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li> 219 * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li> 220 * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li> 221 * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li> 222 * </ul> 223 * @see AccessibilityEvent 224 * @see AccessibilityServiceInfo 225 * @see android.view.accessibility.AccessibilityManager 226 */ 227public abstract class AccessibilityService extends Service { 228 229 /** 230 * The user has performed a swipe up gesture on the touch screen. 231 */ 232 public static final int GESTURE_SWIPE_UP = 1; 233 234 /** 235 * The user has performed a swipe down gesture on the touch screen. 236 */ 237 public static final int GESTURE_SWIPE_DOWN = 2; 238 239 /** 240 * The user has performed a swipe left gesture on the touch screen. 241 */ 242 public static final int GESTURE_SWIPE_LEFT = 3; 243 244 /** 245 * The user has performed a swipe right gesture on the touch screen. 246 */ 247 public static final int GESTURE_SWIPE_RIGHT = 4; 248 249 /** 250 * The user has performed a swipe left and right gesture on the touch screen. 251 */ 252 public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5; 253 254 /** 255 * The user has performed a swipe right and left gesture on the touch screen. 256 */ 257 public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6; 258 259 /** 260 * The user has performed a swipe up and down gesture on the touch screen. 261 */ 262 public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; 263 264 /** 265 * The user has performed a swipe down and up gesture on the touch screen. 266 */ 267 public static final int GESTURE_SWIPE_DOWN_AND_UP = 8; 268 269 /** 270 * The user has performed a left and up gesture on the touch screen. 271 */ 272 public static final int GESTURE_SWIPE_LEFT_AND_UP = 9; 273 274 /** 275 * The user has performed a left and down gesture on the touch screen. 276 */ 277 public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10; 278 279 /** 280 * The user has performed a right and up gesture on the touch screen. 281 */ 282 public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11; 283 284 /** 285 * The user has performed a right and down gesture on the touch screen. 286 */ 287 public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12; 288 289 /** 290 * The user has performed an up and left gesture on the touch screen. 291 */ 292 public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; 293 294 /** 295 * The user has performed an up and right gesture on the touch screen. 296 */ 297 public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; 298 299 /** 300 * The user has performed an down and left gesture on the touch screen. 301 */ 302 public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; 303 304 /** 305 * The user has performed an down and right gesture on the touch screen. 306 */ 307 public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; 308 309 /** 310 * The {@link Intent} that must be declared as handled by the service. 311 */ 312 public static final String SERVICE_INTERFACE = 313 "android.accessibilityservice.AccessibilityService"; 314 315 /** 316 * Name under which an AccessibilityService component publishes information 317 * about itself. This meta-data must reference an XML resource containing an 318 * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code> 319 * tag. This is a a sample XML file configuring an accessibility service: 320 * <pre> <accessibility-service 321 * android:accessibilityEventTypes="typeViewClicked|typeViewFocused" 322 * android:packageNames="foo.bar, foo.baz" 323 * android:accessibilityFeedbackType="feedbackSpoken" 324 * android:notificationTimeout="100" 325 * android:accessibilityFlags="flagDefault" 326 * android:settingsActivity="foo.bar.TestBackActivity" 327 * android:canRetrieveWindowContent="true" 328 * android:canRequestTouchExplorationMode="true" 329 * android:canRequestEnhancedWebAccessibility="true" 330 * . . . 331 * /></pre> 332 */ 333 public static final String SERVICE_META_DATA = "android.accessibilityservice"; 334 335 /** 336 * Action to go back. 337 */ 338 public static final int GLOBAL_ACTION_BACK = 1; 339 340 /** 341 * Action to go home. 342 */ 343 public static final int GLOBAL_ACTION_HOME = 2; 344 345 /** 346 * Action to open the recent apps. 347 */ 348 public static final int GLOBAL_ACTION_RECENTS = 3; 349 350 /** 351 * Action to open the notifications. 352 */ 353 public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; 354 355 /** 356 * Action to open the quick settings. 357 */ 358 public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; 359 360 /** 361 * Action to open the power long-press dialog. 362 */ 363 public static final int GLOBAL_ACTION_POWER_DIALOG = 6; 364 365 private static final String LOG_TAG = "AccessibilityService"; 366 367 /** 368 * @hide 369 */ 370 public interface Callbacks { 371 public void onAccessibilityEvent(AccessibilityEvent event); 372 public void onInterrupt(); 373 public void onServiceConnected(); 374 public void init(int connectionId, IBinder windowToken); 375 public boolean onGesture(int gestureId); 376 public boolean onKeyEvent(KeyEvent event); 377 public void onMagnificationChanged(@NonNull Region region, 378 float scale, float centerX, float centerY); 379 } 380 381 private int mConnectionId; 382 383 private AccessibilityServiceInfo mInfo; 384 385 private IBinder mWindowToken; 386 387 private WindowManager mWindowManager; 388 389 private MagnificationController mMagnificationController; 390 391 /** 392 * Callback for {@link android.view.accessibility.AccessibilityEvent}s. 393 * 394 * @param event An event. 395 */ 396 public abstract void onAccessibilityEvent(AccessibilityEvent event); 397 398 /** 399 * Callback for interrupting the accessibility feedback. 400 */ 401 public abstract void onInterrupt(); 402 403 /** 404 * Dispatches service connection to internal components first, then the 405 * client code. 406 */ 407 private void dispatchServiceConnected() { 408 if (mMagnificationController != null) { 409 mMagnificationController.onServiceConnected(); 410 } 411 412 // The client gets to handle service connection last, after we've set 413 // up any state upon which their code may rely. 414 onServiceConnected(); 415 } 416 417 /** 418 * This method is a part of the {@link AccessibilityService} lifecycle and is 419 * called after the system has successfully bound to the service. If is 420 * convenient to use this method for setting the {@link AccessibilityServiceInfo}. 421 * 422 * @see AccessibilityServiceInfo 423 * @see #setServiceInfo(AccessibilityServiceInfo) 424 */ 425 protected void onServiceConnected() { 426 427 } 428 429 /** 430 * Called by the system when the user performs a specific gesture on the 431 * touch screen. 432 * 433 * <strong>Note:</strong> To receive gestures an accessibility service must 434 * request that the device is in touch exploration mode by setting the 435 * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} 436 * flag. 437 * 438 * @param gestureId The unique id of the performed gesture. 439 * 440 * @return Whether the gesture was handled. 441 * 442 * @see #GESTURE_SWIPE_UP 443 * @see #GESTURE_SWIPE_UP_AND_LEFT 444 * @see #GESTURE_SWIPE_UP_AND_DOWN 445 * @see #GESTURE_SWIPE_UP_AND_RIGHT 446 * @see #GESTURE_SWIPE_DOWN 447 * @see #GESTURE_SWIPE_DOWN_AND_LEFT 448 * @see #GESTURE_SWIPE_DOWN_AND_UP 449 * @see #GESTURE_SWIPE_DOWN_AND_RIGHT 450 * @see #GESTURE_SWIPE_LEFT 451 * @see #GESTURE_SWIPE_LEFT_AND_UP 452 * @see #GESTURE_SWIPE_LEFT_AND_RIGHT 453 * @see #GESTURE_SWIPE_LEFT_AND_DOWN 454 * @see #GESTURE_SWIPE_RIGHT 455 * @see #GESTURE_SWIPE_RIGHT_AND_UP 456 * @see #GESTURE_SWIPE_RIGHT_AND_LEFT 457 * @see #GESTURE_SWIPE_RIGHT_AND_DOWN 458 */ 459 protected boolean onGesture(int gestureId) { 460 return false; 461 } 462 463 /** 464 * Callback that allows an accessibility service to observe the key events 465 * before they are passed to the rest of the system. This means that the events 466 * are first delivered here before they are passed to the device policy, the 467 * input method, or applications. 468 * <p> 469 * <strong>Note:</strong> It is important that key events are handled in such 470 * a way that the event stream that would be passed to the rest of the system 471 * is well-formed. For example, handling the down event but not the up event 472 * and vice versa would generate an inconsistent event stream. 473 * </p> 474 * <p> 475 * <strong>Note:</strong> The key events delivered in this method are copies 476 * and modifying them will have no effect on the events that will be passed 477 * to the system. This method is intended to perform purely filtering 478 * functionality. 479 * <p> 480 * 481 * @param event The event to be processed. 482 * @return If true then the event will be consumed and not delivered to 483 * applications, otherwise it will be delivered as usual. 484 */ 485 protected boolean onKeyEvent(KeyEvent event) { 486 return false; 487 } 488 489 /** 490 * Gets the windows on the screen. This method returns only the windows 491 * that a sighted user can interact with, as opposed to all windows. 492 * For example, if there is a modal dialog shown and the user cannot touch 493 * anything behind it, then only the modal window will be reported 494 * (assuming it is the top one). For convenience the returned windows 495 * are ordered in a descending layer order, which is the windows that 496 * are higher in the Z-order are reported first. Since the user can always 497 * interact with the window that has input focus by typing, the focused 498 * window is always returned (even if covered by a modal window). 499 * <p> 500 * <strong>Note:</strong> In order to access the windows your service has 501 * to declare the capability to retrieve window content by setting the 502 * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} 503 * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. 504 * Also the service has to opt-in to retrieve the interactive windows by 505 * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} 506 * flag. 507 * </p> 508 * 509 * @return The windows if there are windows and the service is can retrieve 510 * them, otherwise an empty list. 511 */ 512 public List<AccessibilityWindowInfo> getWindows() { 513 return AccessibilityInteractionClient.getInstance().getWindows(mConnectionId); 514 } 515 516 /** 517 * Gets the root node in the currently active window if this service 518 * can retrieve window content. The active window is the one that the user 519 * is currently touching or the window with input focus, if the user is not 520 * touching any window. 521 * <p> 522 * <strong>Note:</strong> In order to access the root node your service has 523 * to declare the capability to retrieve window content by setting the 524 * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} 525 * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. 526 * </p> 527 * 528 * @return The root node if this service can retrieve window content. 529 */ 530 public AccessibilityNodeInfo getRootInActiveWindow() { 531 return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId); 532 } 533 534 /** 535 * Returns the magnification controller, which may be used to query and 536 * modify the state of display magnification. 537 * <p> 538 * <strong>Note:</strong> In order to control magnification, your service 539 * must declare the capability by setting the 540 * {@link android.R.styleable#AccessibilityService_canControlMagnification} 541 * property in its meta-data. For more information, see 542 * {@link #SERVICE_META_DATA}. 543 * 544 * @return the magnification controller 545 */ 546 @NonNull 547 public final MagnificationController getMagnificationController() { 548 if (mMagnificationController == null) { 549 mMagnificationController = new MagnificationController(this); 550 } 551 return mMagnificationController; 552 } 553 554 private void onMagnificationChanged(@NonNull Region region, float scale, 555 float centerX, float centerY) { 556 if (mMagnificationController != null) { 557 mMagnificationController.dispatchMagnificationChanged( 558 region, scale, centerX, centerY); 559 } 560 } 561 562 /** 563 * Used to control and query the state of display magnification. 564 */ 565 public static final class MagnificationController { 566 private final AccessibilityService mService; 567 568 /** 569 * Map of listeners to their handlers. Lazily created when adding the 570 * first magnification listener. 571 */ 572 private ArrayMap<OnMagnificationChangedListener, Handler> mListeners; 573 574 MagnificationController(@NonNull AccessibilityService service) { 575 mService = service; 576 } 577 578 /** 579 * Called when the service is connected. 580 */ 581 void onServiceConnected() { 582 if (mListeners != null && !mListeners.isEmpty()) { 583 setMagnificationCallbackEnabled(true); 584 } 585 } 586 587 /** 588 * Adds the specified change listener to the list of magnification 589 * change listeners. The callback will occur on the service's main 590 * thread. 591 * 592 * @param listener the listener to add, must be non-{@code null} 593 */ 594 public void addListener(@NonNull OnMagnificationChangedListener listener) { 595 addListener(listener, null); 596 } 597 598 /** 599 * Adds the specified change listener to the list of magnification 600 * change listeners. The callback will occur on the specified 601 * {@link Handler}'s thread, or on the service's main thread if the 602 * handler is {@code null}. 603 * 604 * @param listener the listener to add, must be non-null 605 * @param handler the handler on which the callback should execute, or 606 * {@code null} to execute on the service's main thread 607 */ 608 public void addListener(@NonNull OnMagnificationChangedListener listener, 609 @Nullable Handler handler) { 610 if (mListeners == null) { 611 mListeners = new ArrayMap<>(); 612 } 613 614 final boolean shouldEnableCallback = mListeners.isEmpty(); 615 mListeners.put(listener, handler); 616 617 if (shouldEnableCallback) { 618 // This may fail if the service is not connected yet, but if we 619 // still have listeners when it connects then we can try again. 620 setMagnificationCallbackEnabled(true); 621 } 622 } 623 624 /** 625 * Removes all instances of the specified change listener from the list 626 * of magnification change listeners. 627 * 628 * @param listener the listener to remove, must be non-null 629 * @return {@code true} if at least one instance of the listener was 630 * removed 631 */ 632 public boolean removeListener(@NonNull OnMagnificationChangedListener listener) { 633 if (mListeners == null) { 634 return false; 635 } 636 637 final int keyIndex = mListeners.indexOfKey(listener); 638 final boolean hasKey = keyIndex >= 0; 639 if (hasKey) { 640 mListeners.removeAt(keyIndex); 641 } 642 643 if (hasKey && mListeners.isEmpty()) { 644 // We just removed the last listener, so we don't need 645 // callbacks from the service anymore. 646 setMagnificationCallbackEnabled(false); 647 } 648 649 return hasKey; 650 } 651 652 private void setMagnificationCallbackEnabled(boolean enabled) { 653 final IAccessibilityServiceConnection connection = 654 AccessibilityInteractionClient.getInstance().getConnection( 655 mService.mConnectionId); 656 if (connection != null) { 657 try { 658 connection.setMagnificationCallbackEnabled(enabled); 659 } catch (RemoteException re) { 660 throw new RuntimeException(re); 661 } 662 } 663 } 664 665 /** 666 * Dispatches magnification changes to any registered listeners. This 667 * should be called on the service's main thread. 668 */ 669 void dispatchMagnificationChanged(final @NonNull Region region, final float scale, 670 final float centerX, final float centerY) { 671 if (mListeners == null || mListeners.isEmpty()) { 672 Slog.d(LOG_TAG, "Received magnification changed " 673 + "callback with no listeners registered!"); 674 setMagnificationCallbackEnabled(false); 675 return; 676 } 677 678 // Listeners may remove themselves. Perform a shallow copy to avoid 679 // concurrent modification. 680 final ArrayMap<OnMagnificationChangedListener, Handler> entries = 681 new ArrayMap<>(mListeners); 682 683 for (int i = 0, count = entries.size(); i < count; i++) { 684 final OnMagnificationChangedListener listener = entries.keyAt(i); 685 final Handler handler = entries.valueAt(i); 686 if (handler != null) { 687 handler.post(new Runnable() { 688 @Override 689 public void run() { 690 listener.onMagnificationChanged(MagnificationController.this, 691 region, scale, centerX, centerY); 692 } 693 }); 694 } else { 695 // We're already on the main thread, just run the listener. 696 listener.onMagnificationChanged(this, region, scale, centerX, centerY); 697 } 698 } 699 } 700 701 /** 702 * Returns the current magnification scale. 703 * <p> 704 * <strong>Note:</strong> If the service is not yet connected (e.g. 705 * {@link AccessibilityService#onServiceConnected()} has not yet been 706 * called) or the service has been disconnected, this method will 707 * return a default value of {@code 1.0f}. 708 * 709 * @return the current magnification scale 710 */ 711 public float getScale() { 712 final IAccessibilityServiceConnection connection = 713 AccessibilityInteractionClient.getInstance().getConnection( 714 mService.mConnectionId); 715 if (connection != null) { 716 try { 717 return connection.getMagnificationScale(); 718 } catch (RemoteException re) { 719 Log.w(LOG_TAG, "Failed to obtain scale", re); 720 } 721 } 722 return 1.0f; 723 } 724 725 /** 726 * Returns the unscaled screen-relative X coordinate of the focal 727 * center of the magnified region. This is the point around which 728 * zooming occurs and is guaranteed to lie within the magnified 729 * region. 730 * <p> 731 * <strong>Note:</strong> If the service is not yet connected (e.g. 732 * {@link AccessibilityService#onServiceConnected()} has not yet been 733 * called) or the service has been disconnected, this method will 734 * return a default value of {@code 0.0f}. 735 * 736 * @return the unscaled screen-relative X coordinate of the center of 737 * the magnified region 738 */ 739 public float getCenterX() { 740 final IAccessibilityServiceConnection connection = 741 AccessibilityInteractionClient.getInstance().getConnection( 742 mService.mConnectionId); 743 if (connection != null) { 744 try { 745 return connection.getMagnificationCenterX(); 746 } catch (RemoteException re) { 747 Log.w(LOG_TAG, "Failed to obtain center X", re); 748 } 749 } 750 return 0.0f; 751 } 752 753 /** 754 * Returns the unscaled screen-relative Y coordinate of the focal 755 * center of the magnified region. This is the point around which 756 * zooming occurs and is guaranteed to lie within the magnified 757 * region. 758 * <p> 759 * <strong>Note:</strong> If the service is not yet connected (e.g. 760 * {@link AccessibilityService#onServiceConnected()} has not yet been 761 * called) or the service has been disconnected, this method will 762 * return a default value of {@code 0.0f}. 763 * 764 * @return the unscaled screen-relative Y coordinate of the center of 765 * the magnified region 766 */ 767 public float getCenterY() { 768 final IAccessibilityServiceConnection connection = 769 AccessibilityInteractionClient.getInstance().getConnection( 770 mService.mConnectionId); 771 if (connection != null) { 772 try { 773 return connection.getMagnificationCenterY(); 774 } catch (RemoteException re) { 775 Log.w(LOG_TAG, "Failed to obtain center Y", re); 776 } 777 } 778 return 0.0f; 779 } 780 781 /** 782 * Returns the region of the screen currently being magnified. If 783 * magnification is not enabled, the returned region will be empty. 784 * <p> 785 * <strong>Note:</strong> If the service is not yet connected (e.g. 786 * {@link AccessibilityService#onServiceConnected()} has not yet been 787 * called) or the service has been disconnected, this method will 788 * return an empty region. 789 * 790 * @return the screen-relative bounds of the magnified region 791 */ 792 @NonNull 793 public Region getMagnifiedRegion() { 794 final IAccessibilityServiceConnection connection = 795 AccessibilityInteractionClient.getInstance().getConnection( 796 mService.mConnectionId); 797 if (connection != null) { 798 try { 799 return connection.getMagnifiedRegion(); 800 } catch (RemoteException re) { 801 Log.w(LOG_TAG, "Failed to obtain magnified region", re); 802 } 803 } 804 return Region.obtain(); 805 } 806 807 /** 808 * Resets magnification scale and center to their default (e.g. no 809 * magnification) values. 810 * <p> 811 * <strong>Note:</strong> If the service is not yet connected (e.g. 812 * {@link AccessibilityService#onServiceConnected()} has not yet been 813 * called) or the service has been disconnected, this method will have 814 * no effect and return {@code false}. 815 * 816 * @param animate {@code true} to animate from the current scale and 817 * center or {@code false} to reset the scale and center 818 * immediately 819 * @return {@code true} on success, {@code false} on failure 820 */ 821 public boolean reset(boolean animate) { 822 final IAccessibilityServiceConnection connection = 823 AccessibilityInteractionClient.getInstance().getConnection( 824 mService.mConnectionId); 825 if (connection != null) { 826 try { 827 return connection.resetMagnification(animate); 828 } catch (RemoteException re) { 829 Log.w(LOG_TAG, "Failed to reset", re); 830 } 831 } 832 return false; 833 } 834 835 /** 836 * Sets the magnification scale. 837 * <p> 838 * <strong>Note:</strong> If the service is not yet connected (e.g. 839 * {@link AccessibilityService#onServiceConnected()} has not yet been 840 * called) or the service has been disconnected, this method will have 841 * no effect and return {@code false}. 842 * 843 * @param scale the magnification scale to set, must be >= 1 and <= 5 844 * @param animate {@code true} to animate from the current scale or 845 * {@code false} to set the scale immediately 846 * @return {@code true} on success, {@code false} on failure 847 */ 848 public boolean setScale(float scale, boolean animate) { 849 final IAccessibilityServiceConnection connection = 850 AccessibilityInteractionClient.getInstance().getConnection( 851 mService.mConnectionId); 852 if (connection != null) { 853 try { 854 return connection.setMagnificationScaleAndCenter( 855 scale, Float.NaN, Float.NaN, animate); 856 } catch (RemoteException re) { 857 Log.w(LOG_TAG, "Failed to set scale", re); 858 } 859 } 860 return false; 861 } 862 863 /** 864 * Sets the center of the magnified viewport. 865 * <p> 866 * <strong>Note:</strong> If the service is not yet connected (e.g. 867 * {@link AccessibilityService#onServiceConnected()} has not yet been 868 * called) or the service has been disconnected, this method will have 869 * no effect and return {@code false}. 870 * 871 * @param centerX the unscaled screen-relative X coordinate on which to 872 * center the viewport 873 * @param centerY the unscaled screen-relative Y coordinate on which to 874 * center the viewport 875 * @param animate {@code true} to animate from the current viewport 876 * center or {@code false} to set the center immediately 877 * @return {@code true} on success, {@code false} on failure 878 */ 879 public boolean setCenter(float centerX, float centerY, boolean animate) { 880 final IAccessibilityServiceConnection connection = 881 AccessibilityInteractionClient.getInstance().getConnection( 882 mService.mConnectionId); 883 if (connection != null) { 884 try { 885 return connection.setMagnificationScaleAndCenter( 886 Float.NaN, centerX, centerY, animate); 887 } catch (RemoteException re) { 888 Log.w(LOG_TAG, "Failed to set center", re); 889 } 890 } 891 return false; 892 } 893 894 /** 895 * Listener for changes in the state of magnification. 896 */ 897 public interface OnMagnificationChangedListener { 898 /** 899 * Called when the magnified region, scale, or center changes. 900 * 901 * @param controller the magnification controller 902 * @param region the new magnified region, may be empty if 903 * magnification is not enabled (e.g. scale is 1) 904 * @param scale the new scale 905 * @param centerX the new X coordinate around which magnification is focused 906 * @param centerY the new Y coordinate around which magnification is focused 907 */ 908 void onMagnificationChanged(@NonNull MagnificationController controller, 909 @NonNull Region region, float scale, float centerX, float centerY); 910 } 911 } 912 913 /** 914 * Performs a global action. Such an action can be performed 915 * at any moment regardless of the current application or user 916 * location in that application. For example going back, going 917 * home, opening recents, etc. 918 * 919 * @param action The action to perform. 920 * @return Whether the action was successfully performed. 921 * 922 * @see #GLOBAL_ACTION_BACK 923 * @see #GLOBAL_ACTION_HOME 924 * @see #GLOBAL_ACTION_NOTIFICATIONS 925 * @see #GLOBAL_ACTION_RECENTS 926 */ 927 public final boolean performGlobalAction(int action) { 928 IAccessibilityServiceConnection connection = 929 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 930 if (connection != null) { 931 try { 932 return connection.performGlobalAction(action); 933 } catch (RemoteException re) { 934 Log.w(LOG_TAG, "Error while calling performGlobalAction", re); 935 } 936 } 937 return false; 938 } 939 940 /** 941 * Find the view that has the specified focus type. The search is performed 942 * across all windows. 943 * <p> 944 * <strong>Note:</strong> In order to access the windows your service has 945 * to declare the capability to retrieve window content by setting the 946 * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} 947 * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. 948 * Also the service has to opt-in to retrieve the interactive windows by 949 * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} 950 * flag.Otherwise, the search will be performed only in the active window. 951 * </p> 952 * 953 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or 954 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. 955 * @return The node info of the focused view or null. 956 * 957 * @see AccessibilityNodeInfo#FOCUS_INPUT 958 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY 959 */ 960 public AccessibilityNodeInfo findFocus(int focus) { 961 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, 962 AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus); 963 } 964 965 /** 966 * Gets the an {@link AccessibilityServiceInfo} describing this 967 * {@link AccessibilityService}. This method is useful if one wants 968 * to change some of the dynamically configurable properties at 969 * runtime. 970 * 971 * @return The accessibility service info. 972 * 973 * @see AccessibilityServiceInfo 974 */ 975 public final AccessibilityServiceInfo getServiceInfo() { 976 IAccessibilityServiceConnection connection = 977 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 978 if (connection != null) { 979 try { 980 return connection.getServiceInfo(); 981 } catch (RemoteException re) { 982 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); 983 } 984 } 985 return null; 986 } 987 988 /** 989 * Sets the {@link AccessibilityServiceInfo} that describes this service. 990 * <p> 991 * Note: You can call this method any time but the info will be picked up after 992 * the system has bound to this service and when this method is called thereafter. 993 * 994 * @param info The info. 995 */ 996 public final void setServiceInfo(AccessibilityServiceInfo info) { 997 mInfo = info; 998 sendServiceInfo(); 999 } 1000 1001 /** 1002 * Sets the {@link AccessibilityServiceInfo} for this service if the latter is 1003 * properly set and there is an {@link IAccessibilityServiceConnection} to the 1004 * AccessibilityManagerService. 1005 */ 1006 private void sendServiceInfo() { 1007 IAccessibilityServiceConnection connection = 1008 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 1009 if (mInfo != null && connection != null) { 1010 try { 1011 connection.setServiceInfo(mInfo); 1012 mInfo = null; 1013 AccessibilityInteractionClient.getInstance().clearCache(); 1014 } catch (RemoteException re) { 1015 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); 1016 } 1017 } 1018 } 1019 1020 @Override 1021 public Object getSystemService(@ServiceName @NonNull String name) { 1022 if (getBaseContext() == null) { 1023 throw new IllegalStateException( 1024 "System services not available to Activities before onCreate()"); 1025 } 1026 1027 // Guarantee that we always return the same window manager instance. 1028 if (WINDOW_SERVICE.equals(name)) { 1029 if (mWindowManager == null) { 1030 mWindowManager = (WindowManager) getBaseContext().getSystemService(name); 1031 } 1032 return mWindowManager; 1033 } 1034 return super.getSystemService(name); 1035 } 1036 1037 /** 1038 * Implement to return the implementation of the internal accessibility 1039 * service interface. 1040 */ 1041 @Override 1042 public final IBinder onBind(Intent intent) { 1043 return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() { 1044 @Override 1045 public void onServiceConnected() { 1046 AccessibilityService.this.dispatchServiceConnected(); 1047 } 1048 1049 @Override 1050 public void onInterrupt() { 1051 AccessibilityService.this.onInterrupt(); 1052 } 1053 1054 @Override 1055 public void onAccessibilityEvent(AccessibilityEvent event) { 1056 AccessibilityService.this.onAccessibilityEvent(event); 1057 } 1058 1059 @Override 1060 public void init(int connectionId, IBinder windowToken) { 1061 mConnectionId = connectionId; 1062 mWindowToken = windowToken; 1063 1064 // The client may have already obtained the window manager, so 1065 // update the default token on whatever manager we gave them. 1066 final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE); 1067 wm.setDefaultToken(windowToken); 1068 } 1069 1070 @Override 1071 public boolean onGesture(int gestureId) { 1072 return AccessibilityService.this.onGesture(gestureId); 1073 } 1074 1075 @Override 1076 public boolean onKeyEvent(KeyEvent event) { 1077 return AccessibilityService.this.onKeyEvent(event); 1078 } 1079 1080 @Override 1081 public void onMagnificationChanged(@NonNull Region region, 1082 float scale, float centerX, float centerY) { 1083 AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY); 1084 } 1085 }); 1086 } 1087 1088 /** 1089 * Implements the internal {@link IAccessibilityServiceClient} interface to convert 1090 * incoming calls to it back to calls on an {@link AccessibilityService}. 1091 * 1092 * @hide 1093 */ 1094 public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub 1095 implements HandlerCaller.Callback { 1096 private static final int DO_INIT = 1; 1097 private static final int DO_ON_INTERRUPT = 2; 1098 private static final int DO_ON_ACCESSIBILITY_EVENT = 3; 1099 private static final int DO_ON_GESTURE = 4; 1100 private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5; 1101 private static final int DO_ON_KEY_EVENT = 6; 1102 private static final int DO_ON_MAGNIFICATION_CHANGED = 7; 1103 1104 private final HandlerCaller mCaller; 1105 1106 private final Callbacks mCallback; 1107 1108 private int mConnectionId; 1109 1110 public IAccessibilityServiceClientWrapper(Context context, Looper looper, 1111 Callbacks callback) { 1112 mCallback = callback; 1113 mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/); 1114 } 1115 1116 public void init(IAccessibilityServiceConnection connection, int connectionId, 1117 IBinder windowToken) { 1118 Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId, 1119 connection, windowToken); 1120 mCaller.sendMessage(message); 1121 } 1122 1123 public void onInterrupt() { 1124 Message message = mCaller.obtainMessage(DO_ON_INTERRUPT); 1125 mCaller.sendMessage(message); 1126 } 1127 1128 public void onAccessibilityEvent(AccessibilityEvent event) { 1129 Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event); 1130 mCaller.sendMessage(message); 1131 } 1132 1133 public void onGesture(int gestureId) { 1134 Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId); 1135 mCaller.sendMessage(message); 1136 } 1137 1138 public void clearAccessibilityCache() { 1139 Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE); 1140 mCaller.sendMessage(message); 1141 } 1142 1143 @Override 1144 public void onKeyEvent(KeyEvent event, int sequence) { 1145 Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event); 1146 mCaller.sendMessage(message); 1147 } 1148 1149 public void onMagnificationChanged(@NonNull Region region, 1150 float scale, float centerX, float centerY) { 1151 final SomeArgs args = SomeArgs.obtain(); 1152 args.arg1 = region; 1153 args.arg2 = scale; 1154 args.arg3 = centerX; 1155 args.arg4 = centerY; 1156 1157 final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args); 1158 mCaller.sendMessage(message); 1159 } 1160 1161 @Override 1162 public void executeMessage(Message message) { 1163 switch (message.what) { 1164 case DO_ON_ACCESSIBILITY_EVENT: { 1165 AccessibilityEvent event = (AccessibilityEvent) message.obj; 1166 if (event != null) { 1167 AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); 1168 mCallback.onAccessibilityEvent(event); 1169 // Make sure the event is recycled. 1170 try { 1171 event.recycle(); 1172 } catch (IllegalStateException ise) { 1173 /* ignore - best effort */ 1174 } 1175 } 1176 } return; 1177 1178 case DO_ON_INTERRUPT: { 1179 mCallback.onInterrupt(); 1180 } return; 1181 1182 case DO_INIT: { 1183 mConnectionId = message.arg1; 1184 SomeArgs args = (SomeArgs) message.obj; 1185 IAccessibilityServiceConnection connection = 1186 (IAccessibilityServiceConnection) args.arg1; 1187 IBinder windowToken = (IBinder) args.arg2; 1188 args.recycle(); 1189 if (connection != null) { 1190 AccessibilityInteractionClient.getInstance().addConnection(mConnectionId, 1191 connection); 1192 mCallback.init(mConnectionId, windowToken); 1193 mCallback.onServiceConnected(); 1194 } else { 1195 AccessibilityInteractionClient.getInstance().removeConnection( 1196 mConnectionId); 1197 mConnectionId = AccessibilityInteractionClient.NO_ID; 1198 AccessibilityInteractionClient.getInstance().clearCache(); 1199 mCallback.init(AccessibilityInteractionClient.NO_ID, null); 1200 } 1201 } return; 1202 1203 case DO_ON_GESTURE: { 1204 final int gestureId = message.arg1; 1205 mCallback.onGesture(gestureId); 1206 } return; 1207 1208 case DO_CLEAR_ACCESSIBILITY_CACHE: { 1209 AccessibilityInteractionClient.getInstance().clearCache(); 1210 } return; 1211 1212 case DO_ON_KEY_EVENT: { 1213 KeyEvent event = (KeyEvent) message.obj; 1214 try { 1215 IAccessibilityServiceConnection connection = AccessibilityInteractionClient 1216 .getInstance().getConnection(mConnectionId); 1217 if (connection != null) { 1218 final boolean result = mCallback.onKeyEvent(event); 1219 final int sequence = message.arg1; 1220 try { 1221 connection.setOnKeyEventResult(result, sequence); 1222 } catch (RemoteException re) { 1223 /* ignore */ 1224 } 1225 } 1226 } finally { 1227 // Make sure the event is recycled. 1228 try { 1229 event.recycle(); 1230 } catch (IllegalStateException ise) { 1231 /* ignore - best effort */ 1232 } 1233 } 1234 } return; 1235 1236 case DO_ON_MAGNIFICATION_CHANGED: { 1237 final SomeArgs args = (SomeArgs) message.obj; 1238 final Region region = (Region) args.arg1; 1239 final float scale = (float) args.arg2; 1240 final float centerX = (float) args.arg3; 1241 final float centerY = (float) args.arg4; 1242 mCallback.onMagnificationChanged(region, scale, centerX, centerY); 1243 } return; 1244 1245 default : 1246 Log.w(LOG_TAG, "Unknown message type " + message.what); 1247 } 1248 } 1249 } 1250} 1251