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 17 package android.view.accessibility; 18 19 import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME; 20 21 import android.Manifest; 22 import android.accessibilityservice.AccessibilityServiceInfo; 23 import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SdkConstant; 27 import android.annotation.SystemService; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ServiceInfo; 32 import android.content.res.Resources; 33 import android.os.Binder; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.Looper; 37 import android.os.Message; 38 import android.os.Process; 39 import android.os.RemoteException; 40 import android.os.ServiceManager; 41 import android.os.SystemClock; 42 import android.os.UserHandle; 43 import android.util.ArrayMap; 44 import android.util.Log; 45 import android.util.SparseArray; 46 import android.view.IWindow; 47 import android.view.View; 48 import android.view.accessibility.AccessibilityEvent.EventType; 49 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.util.IntPair; 52 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.List; 56 57 /** 58 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, 59 * and provides facilities for querying the accessibility state of the system. 60 * Accessibility events are generated when something notable happens in the user interface, 61 * for example an {@link android.app.Activity} starts, the focus or selection of a 62 * {@link android.view.View} changes etc. Parties interested in handling accessibility 63 * events implement and register an accessibility service which extends 64 * {@link android.accessibilityservice.AccessibilityService}. 65 * 66 * @see AccessibilityEvent 67 * @see AccessibilityNodeInfo 68 * @see android.accessibilityservice.AccessibilityService 69 * @see Context#getSystemService 70 * @see Context#ACCESSIBILITY_SERVICE 71 */ 72 @SystemService(Context.ACCESSIBILITY_SERVICE) 73 public final class AccessibilityManager { 74 private static final boolean DEBUG = false; 75 76 private static final String LOG_TAG = "AccessibilityManager"; 77 78 /** @hide */ 79 public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; 80 81 /** @hide */ 82 public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; 83 84 /** @hide */ 85 public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004; 86 87 /** @hide */ 88 public static final int DALTONIZER_DISABLED = -1; 89 90 /** @hide */ 91 public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; 92 93 /** @hide */ 94 public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; 95 96 /** @hide */ 97 public static final int AUTOCLICK_DELAY_DEFAULT = 600; 98 99 /** 100 * Activity action: Launch UI to manage which accessibility service or feature is assigned 101 * to the navigation bar Accessibility button. 102 * <p> 103 * Input: Nothing. 104 * </p> 105 * <p> 106 * Output: Nothing. 107 * </p> 108 * 109 * @hide 110 */ 111 @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) 112 public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = 113 "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; 114 115 static final Object sInstanceSync = new Object(); 116 117 private static AccessibilityManager sInstance; 118 119 private final Object mLock = new Object(); 120 121 private IAccessibilityManager mService; 122 123 final int mUserId; 124 125 final Handler mHandler; 126 127 final Handler.Callback mCallback; 128 129 boolean mIsEnabled; 130 131 int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; 132 133 boolean mIsTouchExplorationEnabled; 134 135 boolean mIsHighTextContrastEnabled; 136 137 AccessibilityPolicy mAccessibilityPolicy; 138 139 private final ArrayMap<AccessibilityStateChangeListener, Handler> 140 mAccessibilityStateChangeListeners = new ArrayMap<>(); 141 142 private final ArrayMap<TouchExplorationStateChangeListener, Handler> 143 mTouchExplorationStateChangeListeners = new ArrayMap<>(); 144 145 private final ArrayMap<HighTextContrastChangeListener, Handler> 146 mHighTextContrastStateChangeListeners = new ArrayMap<>(); 147 148 private final ArrayMap<AccessibilityServicesStateChangeListener, Handler> 149 mServicesStateChangeListeners = new ArrayMap<>(); 150 151 /** 152 * Map from a view's accessibility id to the list of request preparers set for that view 153 */ 154 private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists; 155 156 /** 157 * Listener for the system accessibility state. To listen for changes to the 158 * accessibility state on the device, implement this interface and register 159 * it with the system by calling {@link #addAccessibilityStateChangeListener}. 160 */ 161 public interface AccessibilityStateChangeListener { 162 163 /** 164 * Called when the accessibility enabled state changes. 165 * 166 * @param enabled Whether accessibility is enabled. 167 */ onAccessibilityStateChanged(boolean enabled)168 void onAccessibilityStateChanged(boolean enabled); 169 } 170 171 /** 172 * Listener for the system touch exploration state. To listen for changes to 173 * the touch exploration state on the device, implement this interface and 174 * register it with the system by calling 175 * {@link #addTouchExplorationStateChangeListener}. 176 */ 177 public interface TouchExplorationStateChangeListener { 178 179 /** 180 * Called when the touch exploration enabled state changes. 181 * 182 * @param enabled Whether touch exploration is enabled. 183 */ onTouchExplorationStateChanged(boolean enabled)184 void onTouchExplorationStateChanged(boolean enabled); 185 } 186 187 /** 188 * Listener for changes to the state of accessibility services. Changes include services being 189 * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service. 190 * {@see #addAccessibilityServicesStateChangeListener}. 191 * 192 * @hide 193 */ 194 public interface AccessibilityServicesStateChangeListener { 195 196 /** 197 * Called when the state of accessibility services changes. 198 * 199 * @param manager The manager that is calling back 200 */ onAccessibilityServicesStateChanged(AccessibilityManager manager)201 void onAccessibilityServicesStateChanged(AccessibilityManager manager); 202 } 203 204 /** 205 * Listener for the system high text contrast state. To listen for changes to 206 * the high text contrast state on the device, implement this interface and 207 * register it with the system by calling 208 * {@link #addHighTextContrastStateChangeListener}. 209 * 210 * @hide 211 */ 212 public interface HighTextContrastChangeListener { 213 214 /** 215 * Called when the high text contrast enabled state changes. 216 * 217 * @param enabled Whether high text contrast is enabled. 218 */ onHighTextContrastStateChanged(boolean enabled)219 void onHighTextContrastStateChanged(boolean enabled); 220 } 221 222 /** 223 * Policy to inject behavior into the accessibility manager. 224 * 225 * @hide 226 */ 227 public interface AccessibilityPolicy { 228 /** 229 * Checks whether accessibility is enabled. 230 * 231 * @param accessibilityEnabled Whether the accessibility layer is enabled. 232 * @return whether accessibility is enabled. 233 */ isEnabled(boolean accessibilityEnabled)234 boolean isEnabled(boolean accessibilityEnabled); 235 236 /** 237 * Notifies the policy for an accessibility event. 238 * 239 * @param event The event. 240 * @param accessibilityEnabled Whether the accessibility layer is enabled. 241 * @param relevantEventTypes The events relevant events. 242 * @return The event to dispatch or null. 243 */ onAccessibilityEvent(@onNull AccessibilityEvent event, boolean accessibilityEnabled, @EventType int relevantEventTypes)244 @Nullable AccessibilityEvent onAccessibilityEvent(@NonNull AccessibilityEvent event, 245 boolean accessibilityEnabled, @EventType int relevantEventTypes); 246 247 /** 248 * Gets the list of relevant events. 249 * 250 * @param relevantEventTypes The relevant events. 251 * @return The relevant events to report. 252 */ getRelevantEventTypes(@ventType int relevantEventTypes)253 @EventType int getRelevantEventTypes(@EventType int relevantEventTypes); 254 255 /** 256 * Gets the list of installed services to report. 257 * 258 * @param installedService The installed services. 259 * @return The services to report. 260 */ getInstalledAccessibilityServiceList( @ullable List<AccessibilityServiceInfo> installedService)261 @NonNull List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList( 262 @Nullable List<AccessibilityServiceInfo> installedService); 263 264 /** 265 * Gets the list of enabled accessibility services. 266 * 267 * @param feedbackTypeFlags The feedback type to query for. 268 * @param enabledService The enabled services. 269 * @return The services to report. 270 */ getEnabledAccessibilityServiceList( @eedbackType int feedbackTypeFlags, @Nullable List<AccessibilityServiceInfo> enabledService)271 @Nullable List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 272 @FeedbackType int feedbackTypeFlags, 273 @Nullable List<AccessibilityServiceInfo> enabledService); 274 } 275 276 private final IAccessibilityManagerClient.Stub mClient = 277 new IAccessibilityManagerClient.Stub() { 278 @Override 279 public void setState(int state) { 280 // We do not want to change this immediately as the application may 281 // have already checked that accessibility is on and fired an event, 282 // that is now propagating up the view tree, Hence, if accessibility 283 // is now off an exception will be thrown. We want to have the exception 284 // enforcement to guard against apps that fire unnecessary accessibility 285 // events when accessibility is off. 286 mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget(); 287 } 288 289 @Override 290 public void notifyServicesStateChanged() { 291 final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners; 292 synchronized (mLock) { 293 if (mServicesStateChangeListeners.isEmpty()) { 294 return; 295 } 296 listeners = new ArrayMap<>(mServicesStateChangeListeners); 297 } 298 299 int numListeners = listeners.size(); 300 for (int i = 0; i < numListeners; i++) { 301 final AccessibilityServicesStateChangeListener listener = 302 mServicesStateChangeListeners.keyAt(i); 303 mServicesStateChangeListeners.valueAt(i).post(() -> listener 304 .onAccessibilityServicesStateChanged(AccessibilityManager.this)); 305 } 306 } 307 308 @Override 309 public void setRelevantEventTypes(int eventTypes) { 310 mRelevantEventTypes = eventTypes; 311 } 312 }; 313 314 /** 315 * Get an AccessibilityManager instance (create one if necessary). 316 * 317 * @param context Context in which this manager operates. 318 * 319 * @hide 320 */ getInstance(Context context)321 public static AccessibilityManager getInstance(Context context) { 322 synchronized (sInstanceSync) { 323 if (sInstance == null) { 324 final int userId; 325 if (Binder.getCallingUid() == Process.SYSTEM_UID 326 || context.checkCallingOrSelfPermission( 327 Manifest.permission.INTERACT_ACROSS_USERS) 328 == PackageManager.PERMISSION_GRANTED 329 || context.checkCallingOrSelfPermission( 330 Manifest.permission.INTERACT_ACROSS_USERS_FULL) 331 == PackageManager.PERMISSION_GRANTED) { 332 userId = UserHandle.USER_CURRENT; 333 } else { 334 userId = context.getUserId(); 335 } 336 sInstance = new AccessibilityManager(context, null, userId); 337 } 338 } 339 return sInstance; 340 } 341 342 /** 343 * Create an instance. 344 * 345 * @param context A {@link Context}. 346 * @param service An interface to the backing service. 347 * @param userId User id under which to run. 348 * 349 * @hide 350 */ AccessibilityManager(Context context, IAccessibilityManager service, int userId)351 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { 352 // Constructor can't be chained because we can't create an instance of an inner class 353 // before calling another constructor. 354 mCallback = new MyCallback(); 355 mHandler = new Handler(context.getMainLooper(), mCallback); 356 mUserId = userId; 357 synchronized (mLock) { 358 tryConnectToServiceLocked(service); 359 } 360 } 361 362 /** 363 * Create an instance. 364 * 365 * @param handler The handler to use 366 * @param service An interface to the backing service. 367 * @param userId User id under which to run. 368 * 369 * @hide 370 */ AccessibilityManager(Handler handler, IAccessibilityManager service, int userId)371 public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) { 372 mCallback = new MyCallback(); 373 mHandler = handler; 374 mUserId = userId; 375 synchronized (mLock) { 376 tryConnectToServiceLocked(service); 377 } 378 } 379 380 /** 381 * @hide 382 */ getClient()383 public IAccessibilityManagerClient getClient() { 384 return mClient; 385 } 386 387 /** 388 * @hide 389 */ 390 @VisibleForTesting getCallback()391 public Handler.Callback getCallback() { 392 return mCallback; 393 } 394 395 /** 396 * Returns if the accessibility in the system is enabled. 397 * 398 * @return True if accessibility is enabled, false otherwise. 399 */ isEnabled()400 public boolean isEnabled() { 401 synchronized (mLock) { 402 return mIsEnabled || (mAccessibilityPolicy != null 403 && mAccessibilityPolicy.isEnabled(mIsEnabled)); 404 } 405 } 406 407 /** 408 * Returns if the touch exploration in the system is enabled. 409 * 410 * @return True if touch exploration is enabled, false otherwise. 411 */ isTouchExplorationEnabled()412 public boolean isTouchExplorationEnabled() { 413 synchronized (mLock) { 414 IAccessibilityManager service = getServiceLocked(); 415 if (service == null) { 416 return false; 417 } 418 return mIsTouchExplorationEnabled; 419 } 420 } 421 422 /** 423 * Returns if the high text contrast in the system is enabled. 424 * <p> 425 * <strong>Note:</strong> You need to query this only if you application is 426 * doing its own rendering and does not rely on the platform rendering pipeline. 427 * </p> 428 * 429 * @return True if high text contrast is enabled, false otherwise. 430 * 431 * @hide 432 */ isHighTextContrastEnabled()433 public boolean isHighTextContrastEnabled() { 434 synchronized (mLock) { 435 IAccessibilityManager service = getServiceLocked(); 436 if (service == null) { 437 return false; 438 } 439 return mIsHighTextContrastEnabled; 440 } 441 } 442 443 /** 444 * Sends an {@link AccessibilityEvent}. 445 * 446 * @param event The event to send. 447 * 448 * @throws IllegalStateException if accessibility is not enabled. 449 * 450 * <strong>Note:</strong> The preferred mechanism for sending custom accessibility 451 * events is through calling 452 * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} 453 * instead of this method to allow predecessors to augment/filter events sent by 454 * their descendants. 455 */ sendAccessibilityEvent(AccessibilityEvent event)456 public void sendAccessibilityEvent(AccessibilityEvent event) { 457 final IAccessibilityManager service; 458 final int userId; 459 final AccessibilityEvent dispatchedEvent; 460 synchronized (mLock) { 461 service = getServiceLocked(); 462 if (service == null) { 463 return; 464 } 465 event.setEventTime(SystemClock.uptimeMillis()); 466 if (mAccessibilityPolicy != null) { 467 dispatchedEvent = mAccessibilityPolicy.onAccessibilityEvent(event, 468 mIsEnabled, mRelevantEventTypes); 469 if (dispatchedEvent == null) { 470 return; 471 } 472 } else { 473 dispatchedEvent = event; 474 } 475 if (!isEnabled()) { 476 Looper myLooper = Looper.myLooper(); 477 if (myLooper == Looper.getMainLooper()) { 478 throw new IllegalStateException( 479 "Accessibility off. Did you forget to check that?"); 480 } else { 481 // If we're not running on the thread with the main looper, it's possible for 482 // the state of accessibility to change between checking isEnabled and 483 // calling this method. So just log the error rather than throwing the 484 // exception. 485 Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled"); 486 return; 487 } 488 } 489 if ((dispatchedEvent.getEventType() & mRelevantEventTypes) == 0) { 490 if (DEBUG) { 491 Log.i(LOG_TAG, "Not dispatching irrelevant event: " + dispatchedEvent 492 + " that is not among " 493 + AccessibilityEvent.eventTypeToString(mRelevantEventTypes)); 494 } 495 return; 496 } 497 userId = mUserId; 498 } 499 try { 500 // it is possible that this manager is in the same process as the service but 501 // client using it is called through Binder from another process. Example: MMS 502 // app adds a SMS notification and the NotificationManagerService calls this method 503 long identityToken = Binder.clearCallingIdentity(); 504 try { 505 service.sendAccessibilityEvent(dispatchedEvent, userId); 506 } finally { 507 Binder.restoreCallingIdentity(identityToken); 508 } 509 if (DEBUG) { 510 Log.i(LOG_TAG, dispatchedEvent + " sent"); 511 } 512 } catch (RemoteException re) { 513 Log.e(LOG_TAG, "Error during sending " + dispatchedEvent + " ", re); 514 } finally { 515 if (event != dispatchedEvent) { 516 event.recycle(); 517 } 518 dispatchedEvent.recycle(); 519 } 520 } 521 522 /** 523 * Requests feedback interruption from all accessibility services. 524 */ interrupt()525 public void interrupt() { 526 final IAccessibilityManager service; 527 final int userId; 528 synchronized (mLock) { 529 service = getServiceLocked(); 530 if (service == null) { 531 return; 532 } 533 if (!isEnabled()) { 534 Looper myLooper = Looper.myLooper(); 535 if (myLooper == Looper.getMainLooper()) { 536 throw new IllegalStateException( 537 "Accessibility off. Did you forget to check that?"); 538 } else { 539 // If we're not running on the thread with the main looper, it's possible for 540 // the state of accessibility to change between checking isEnabled and 541 // calling this method. So just log the error rather than throwing the 542 // exception. 543 Log.e(LOG_TAG, "Interrupt called with accessibility disabled"); 544 return; 545 } 546 } 547 userId = mUserId; 548 } 549 try { 550 service.interrupt(userId); 551 if (DEBUG) { 552 Log.i(LOG_TAG, "Requested interrupt from all services"); 553 } 554 } catch (RemoteException re) { 555 Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); 556 } 557 } 558 559 /** 560 * Returns the {@link ServiceInfo}s of the installed accessibility services. 561 * 562 * @return An unmodifiable list with {@link ServiceInfo}s. 563 * 564 * @deprecated Use {@link #getInstalledAccessibilityServiceList()} 565 */ 566 @Deprecated getAccessibilityServiceList()567 public List<ServiceInfo> getAccessibilityServiceList() { 568 List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); 569 List<ServiceInfo> services = new ArrayList<>(); 570 final int infoCount = infos.size(); 571 for (int i = 0; i < infoCount; i++) { 572 AccessibilityServiceInfo info = infos.get(i); 573 services.add(info.getResolveInfo().serviceInfo); 574 } 575 return Collections.unmodifiableList(services); 576 } 577 578 /** 579 * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. 580 * 581 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 582 */ getInstalledAccessibilityServiceList()583 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { 584 final IAccessibilityManager service; 585 final int userId; 586 synchronized (mLock) { 587 service = getServiceLocked(); 588 if (service == null) { 589 return Collections.emptyList(); 590 } 591 userId = mUserId; 592 } 593 594 List<AccessibilityServiceInfo> services = null; 595 try { 596 services = service.getInstalledAccessibilityServiceList(userId); 597 if (DEBUG) { 598 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 599 } 600 } catch (RemoteException re) { 601 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 602 } 603 if (mAccessibilityPolicy != null) { 604 services = mAccessibilityPolicy.getInstalledAccessibilityServiceList(services); 605 } 606 if (services != null) { 607 return Collections.unmodifiableList(services); 608 } else { 609 return Collections.emptyList(); 610 } 611 } 612 613 /** 614 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services 615 * for a given feedback type. 616 * 617 * @param feedbackTypeFlags The feedback type flags. 618 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 619 * 620 * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE 621 * @see AccessibilityServiceInfo#FEEDBACK_GENERIC 622 * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC 623 * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN 624 * @see AccessibilityServiceInfo#FEEDBACK_VISUAL 625 * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE 626 */ getEnabledAccessibilityServiceList( int feedbackTypeFlags)627 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 628 int feedbackTypeFlags) { 629 final IAccessibilityManager service; 630 final int userId; 631 synchronized (mLock) { 632 service = getServiceLocked(); 633 if (service == null) { 634 return Collections.emptyList(); 635 } 636 userId = mUserId; 637 } 638 639 List<AccessibilityServiceInfo> services = null; 640 try { 641 services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); 642 if (DEBUG) { 643 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 644 } 645 } catch (RemoteException re) { 646 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 647 } 648 if (mAccessibilityPolicy != null) { 649 services = mAccessibilityPolicy.getEnabledAccessibilityServiceList( 650 feedbackTypeFlags, services); 651 } 652 if (services != null) { 653 return Collections.unmodifiableList(services); 654 } else { 655 return Collections.emptyList(); 656 } 657 } 658 659 /** 660 * Registers an {@link AccessibilityStateChangeListener} for changes in 661 * the global accessibility state of the system. Equivalent to calling 662 * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)} 663 * with a null handler. 664 * 665 * @param listener The listener. 666 * @return Always returns {@code true}. 667 */ addAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)668 public boolean addAccessibilityStateChangeListener( 669 @NonNull AccessibilityStateChangeListener listener) { 670 addAccessibilityStateChangeListener(listener, null); 671 return true; 672 } 673 674 /** 675 * Registers an {@link AccessibilityStateChangeListener} for changes in 676 * the global accessibility state of the system. If the listener has already been registered, 677 * the handler used to call it back is updated. 678 * 679 * @param listener The listener. 680 * @param handler The handler on which the listener should be called back, or {@code null} 681 * for a callback on the process's main handler. 682 */ addAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener, @Nullable Handler handler)683 public void addAccessibilityStateChangeListener( 684 @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) { 685 synchronized (mLock) { 686 mAccessibilityStateChangeListeners 687 .put(listener, (handler == null) ? mHandler : handler); 688 } 689 } 690 691 /** 692 * Unregisters an {@link AccessibilityStateChangeListener}. 693 * 694 * @param listener The listener. 695 * @return True if the listener was previously registered. 696 */ removeAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)697 public boolean removeAccessibilityStateChangeListener( 698 @NonNull AccessibilityStateChangeListener listener) { 699 synchronized (mLock) { 700 int index = mAccessibilityStateChangeListeners.indexOfKey(listener); 701 mAccessibilityStateChangeListeners.remove(listener); 702 return (index >= 0); 703 } 704 } 705 706 /** 707 * Registers a {@link TouchExplorationStateChangeListener} for changes in 708 * the global touch exploration state of the system. Equivalent to calling 709 * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)} 710 * with a null handler. 711 * 712 * @param listener The listener. 713 * @return Always returns {@code true}. 714 */ addTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)715 public boolean addTouchExplorationStateChangeListener( 716 @NonNull TouchExplorationStateChangeListener listener) { 717 addTouchExplorationStateChangeListener(listener, null); 718 return true; 719 } 720 721 /** 722 * Registers an {@link TouchExplorationStateChangeListener} for changes in 723 * the global touch exploration state of the system. If the listener has already been 724 * registered, the handler used to call it back is updated. 725 * 726 * @param listener The listener. 727 * @param handler The handler on which the listener should be called back, or {@code null} 728 * for a callback on the process's main handler. 729 */ addTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener, @Nullable Handler handler)730 public void addTouchExplorationStateChangeListener( 731 @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) { 732 synchronized (mLock) { 733 mTouchExplorationStateChangeListeners 734 .put(listener, (handler == null) ? mHandler : handler); 735 } 736 } 737 738 /** 739 * Unregisters a {@link TouchExplorationStateChangeListener}. 740 * 741 * @param listener The listener. 742 * @return True if listener was previously registered. 743 */ removeTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)744 public boolean removeTouchExplorationStateChangeListener( 745 @NonNull TouchExplorationStateChangeListener listener) { 746 synchronized (mLock) { 747 int index = mTouchExplorationStateChangeListeners.indexOfKey(listener); 748 mTouchExplorationStateChangeListeners.remove(listener); 749 return (index >= 0); 750 } 751 } 752 753 /** 754 * Registers a {@link AccessibilityServicesStateChangeListener}. 755 * 756 * @param listener The listener. 757 * @param handler The handler on which the listener should be called back, or {@code null} 758 * for a callback on the process's main handler. 759 * @hide 760 */ addAccessibilityServicesStateChangeListener( @onNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler)761 public void addAccessibilityServicesStateChangeListener( 762 @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { 763 synchronized (mLock) { 764 mServicesStateChangeListeners 765 .put(listener, (handler == null) ? mHandler : handler); 766 } 767 } 768 769 /** 770 * Unregisters a {@link AccessibilityServicesStateChangeListener}. 771 * 772 * @param listener The listener. 773 * 774 * @hide 775 */ removeAccessibilityServicesStateChangeListener( @onNull AccessibilityServicesStateChangeListener listener)776 public void removeAccessibilityServicesStateChangeListener( 777 @NonNull AccessibilityServicesStateChangeListener listener) { 778 synchronized (mLock) { 779 mServicesStateChangeListeners.remove(listener); 780 } 781 } 782 783 /** 784 * Registers a {@link AccessibilityRequestPreparer}. 785 */ addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer)786 public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { 787 if (mRequestPreparerLists == null) { 788 mRequestPreparerLists = new SparseArray<>(1); 789 } 790 int id = preparer.getView().getAccessibilityViewId(); 791 List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id); 792 if (requestPreparerList == null) { 793 requestPreparerList = new ArrayList<>(1); 794 mRequestPreparerLists.put(id, requestPreparerList); 795 } 796 requestPreparerList.add(preparer); 797 } 798 799 /** 800 * Unregisters a {@link AccessibilityRequestPreparer}. 801 */ removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer)802 public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { 803 if (mRequestPreparerLists == null) { 804 return; 805 } 806 int viewId = preparer.getView().getAccessibilityViewId(); 807 List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId); 808 if (requestPreparerList != null) { 809 requestPreparerList.remove(preparer); 810 if (requestPreparerList.isEmpty()) { 811 mRequestPreparerLists.remove(viewId); 812 } 813 } 814 } 815 816 /** 817 * Get the preparers that are registered for an accessibility ID 818 * 819 * @param id The ID of interest 820 * @return The list of preparers, or {@code null} if there are none. 821 * 822 * @hide 823 */ getRequestPreparersForAccessibilityId(int id)824 public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) { 825 if (mRequestPreparerLists == null) { 826 return null; 827 } 828 return mRequestPreparerLists.get(id); 829 } 830 831 /** 832 * Registers a {@link HighTextContrastChangeListener} for changes in 833 * the global high text contrast state of the system. 834 * 835 * @param listener The listener. 836 * 837 * @hide 838 */ addHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener, @Nullable Handler handler)839 public void addHighTextContrastStateChangeListener( 840 @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) { 841 synchronized (mLock) { 842 mHighTextContrastStateChangeListeners 843 .put(listener, (handler == null) ? mHandler : handler); 844 } 845 } 846 847 /** 848 * Unregisters a {@link HighTextContrastChangeListener}. 849 * 850 * @param listener The listener. 851 * 852 * @hide 853 */ removeHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener)854 public void removeHighTextContrastStateChangeListener( 855 @NonNull HighTextContrastChangeListener listener) { 856 synchronized (mLock) { 857 mHighTextContrastStateChangeListeners.remove(listener); 858 } 859 } 860 861 /** 862 * Sets the {@link AccessibilityPolicy} controlling this manager. 863 * 864 * @param policy The policy. 865 * 866 * @hide 867 */ setAccessibilityPolicy(@ullable AccessibilityPolicy policy)868 public void setAccessibilityPolicy(@Nullable AccessibilityPolicy policy) { 869 synchronized (mLock) { 870 mAccessibilityPolicy = policy; 871 } 872 } 873 874 /** 875 * Check if the accessibility volume stream is active. 876 * 877 * @return True if accessibility volume is active (i.e. some service has requested it). False 878 * otherwise. 879 * @hide 880 */ isAccessibilityVolumeStreamActive()881 public boolean isAccessibilityVolumeStreamActive() { 882 List<AccessibilityServiceInfo> serviceInfos = 883 getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); 884 for (int i = 0; i < serviceInfos.size(); i++) { 885 if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) { 886 return true; 887 } 888 } 889 return false; 890 } 891 892 /** 893 * Report a fingerprint gesture to accessibility. Only available for the system process. 894 * 895 * @param keyCode The key code of the gesture 896 * @return {@code true} if accessibility consumes the event. {@code false} if not. 897 * @hide 898 */ sendFingerprintGesture(int keyCode)899 public boolean sendFingerprintGesture(int keyCode) { 900 final IAccessibilityManager service; 901 synchronized (mLock) { 902 service = getServiceLocked(); 903 if (service == null) { 904 return false; 905 } 906 } 907 try { 908 return service.sendFingerprintGesture(keyCode); 909 } catch (RemoteException e) { 910 return false; 911 } 912 } 913 914 /** 915 * Sets the current state and notifies listeners, if necessary. 916 * 917 * @param stateFlags The state flags. 918 */ setStateLocked(int stateFlags)919 private void setStateLocked(int stateFlags) { 920 final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; 921 final boolean touchExplorationEnabled = 922 (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; 923 final boolean highTextContrastEnabled = 924 (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; 925 926 final boolean wasEnabled = isEnabled(); 927 final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; 928 final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; 929 930 // Ensure listeners get current state from isZzzEnabled() calls. 931 mIsEnabled = enabled; 932 mIsTouchExplorationEnabled = touchExplorationEnabled; 933 mIsHighTextContrastEnabled = highTextContrastEnabled; 934 935 if (wasEnabled != isEnabled()) { 936 notifyAccessibilityStateChanged(); 937 } 938 939 if (wasTouchExplorationEnabled != touchExplorationEnabled) { 940 notifyTouchExplorationStateChanged(); 941 } 942 943 if (wasHighTextContrastEnabled != highTextContrastEnabled) { 944 notifyHighTextContrastStateChanged(); 945 } 946 } 947 948 /** 949 * Find an installed service with the specified {@link ComponentName}. 950 * 951 * @param componentName The name to match to the service. 952 * 953 * @return The info corresponding to the installed service, or {@code null} if no such service 954 * is installed. 955 * @hide 956 */ getInstalledServiceInfoWithComponentName( ComponentName componentName)957 public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( 958 ComponentName componentName) { 959 final List<AccessibilityServiceInfo> installedServiceInfos = 960 getInstalledAccessibilityServiceList(); 961 if ((installedServiceInfos == null) || (componentName == null)) { 962 return null; 963 } 964 for (int i = 0; i < installedServiceInfos.size(); i++) { 965 if (componentName.equals(installedServiceInfos.get(i).getComponentName())) { 966 return installedServiceInfos.get(i); 967 } 968 } 969 return null; 970 } 971 972 /** 973 * Adds an accessibility interaction connection interface for a given window. 974 * @param windowToken The window token to which a connection is added. 975 * @param connection The connection. 976 * 977 * @hide 978 */ addAccessibilityInteractionConnection(IWindow windowToken, String packageName, IAccessibilityInteractionConnection connection)979 public int addAccessibilityInteractionConnection(IWindow windowToken, 980 String packageName, IAccessibilityInteractionConnection connection) { 981 final IAccessibilityManager service; 982 final int userId; 983 synchronized (mLock) { 984 service = getServiceLocked(); 985 if (service == null) { 986 return View.NO_ID; 987 } 988 userId = mUserId; 989 } 990 try { 991 return service.addAccessibilityInteractionConnection(windowToken, connection, 992 packageName, userId); 993 } catch (RemoteException re) { 994 Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); 995 } 996 return View.NO_ID; 997 } 998 999 /** 1000 * Removed an accessibility interaction connection interface for a given window. 1001 * @param windowToken The window token to which a connection is removed. 1002 * 1003 * @hide 1004 */ removeAccessibilityInteractionConnection(IWindow windowToken)1005 public void removeAccessibilityInteractionConnection(IWindow windowToken) { 1006 final IAccessibilityManager service; 1007 synchronized (mLock) { 1008 service = getServiceLocked(); 1009 if (service == null) { 1010 return; 1011 } 1012 } 1013 try { 1014 service.removeAccessibilityInteractionConnection(windowToken); 1015 } catch (RemoteException re) { 1016 Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); 1017 } 1018 } 1019 1020 /** 1021 * Perform the accessibility shortcut if the caller has permission. 1022 * 1023 * @hide 1024 */ performAccessibilityShortcut()1025 public void performAccessibilityShortcut() { 1026 final IAccessibilityManager service; 1027 synchronized (mLock) { 1028 service = getServiceLocked(); 1029 if (service == null) { 1030 return; 1031 } 1032 } 1033 try { 1034 service.performAccessibilityShortcut(); 1035 } catch (RemoteException re) { 1036 Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re); 1037 } 1038 } 1039 1040 /** 1041 * Notifies that the accessibility button in the system's navigation area has been clicked 1042 * 1043 * @hide 1044 */ notifyAccessibilityButtonClicked()1045 public void notifyAccessibilityButtonClicked() { 1046 final IAccessibilityManager service; 1047 synchronized (mLock) { 1048 service = getServiceLocked(); 1049 if (service == null) { 1050 return; 1051 } 1052 } 1053 try { 1054 service.notifyAccessibilityButtonClicked(); 1055 } catch (RemoteException re) { 1056 Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); 1057 } 1058 } 1059 1060 /** 1061 * Notifies that the visibility of the accessibility button in the system's navigation area 1062 * has changed. 1063 * 1064 * @param shown {@code true} if the accessibility button is visible within the system 1065 * navigation area, {@code false} otherwise 1066 * @hide 1067 */ notifyAccessibilityButtonVisibilityChanged(boolean shown)1068 public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { 1069 final IAccessibilityManager service; 1070 synchronized (mLock) { 1071 service = getServiceLocked(); 1072 if (service == null) { 1073 return; 1074 } 1075 } 1076 try { 1077 service.notifyAccessibilityButtonVisibilityChanged(shown); 1078 } catch (RemoteException re) { 1079 Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re); 1080 } 1081 } 1082 1083 /** 1084 * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture 1085 * window. Intended for use by the System UI only. 1086 * 1087 * @param connection The connection to handle the actions. Set to {@code null} to avoid 1088 * affecting the actions. 1089 * 1090 * @hide 1091 */ setPictureInPictureActionReplacingConnection( @ullable IAccessibilityInteractionConnection connection)1092 public void setPictureInPictureActionReplacingConnection( 1093 @Nullable IAccessibilityInteractionConnection connection) { 1094 final IAccessibilityManager service; 1095 synchronized (mLock) { 1096 service = getServiceLocked(); 1097 if (service == null) { 1098 return; 1099 } 1100 } 1101 try { 1102 service.setPictureInPictureActionReplacingConnection(connection); 1103 } catch (RemoteException re) { 1104 Log.e(LOG_TAG, "Error setting picture in picture action replacement", re); 1105 } 1106 } 1107 getServiceLocked()1108 private IAccessibilityManager getServiceLocked() { 1109 if (mService == null) { 1110 tryConnectToServiceLocked(null); 1111 } 1112 return mService; 1113 } 1114 tryConnectToServiceLocked(IAccessibilityManager service)1115 private void tryConnectToServiceLocked(IAccessibilityManager service) { 1116 if (service == null) { 1117 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); 1118 if (iBinder == null) { 1119 return; 1120 } 1121 service = IAccessibilityManager.Stub.asInterface(iBinder); 1122 } 1123 1124 try { 1125 final long userStateAndRelevantEvents = service.addClient(mClient, mUserId); 1126 setStateLocked(IntPair.first(userStateAndRelevantEvents)); 1127 mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); 1128 mService = service; 1129 } catch (RemoteException re) { 1130 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); 1131 } 1132 } 1133 1134 /** 1135 * Notifies the registered {@link AccessibilityStateChangeListener}s. 1136 */ notifyAccessibilityStateChanged()1137 private void notifyAccessibilityStateChanged() { 1138 final boolean isEnabled; 1139 final ArrayMap<AccessibilityStateChangeListener, Handler> listeners; 1140 synchronized (mLock) { 1141 if (mAccessibilityStateChangeListeners.isEmpty()) { 1142 return; 1143 } 1144 isEnabled = isEnabled(); 1145 listeners = new ArrayMap<>(mAccessibilityStateChangeListeners); 1146 } 1147 1148 final int numListeners = listeners.size(); 1149 for (int i = 0; i < numListeners; i++) { 1150 final AccessibilityStateChangeListener listener = listeners.keyAt(i); 1151 listeners.valueAt(i).post(() -> 1152 listener.onAccessibilityStateChanged(isEnabled)); 1153 } 1154 } 1155 1156 /** 1157 * Notifies the registered {@link TouchExplorationStateChangeListener}s. 1158 */ notifyTouchExplorationStateChanged()1159 private void notifyTouchExplorationStateChanged() { 1160 final boolean isTouchExplorationEnabled; 1161 final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners; 1162 synchronized (mLock) { 1163 if (mTouchExplorationStateChangeListeners.isEmpty()) { 1164 return; 1165 } 1166 isTouchExplorationEnabled = mIsTouchExplorationEnabled; 1167 listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners); 1168 } 1169 1170 final int numListeners = listeners.size(); 1171 for (int i = 0; i < numListeners; i++) { 1172 final TouchExplorationStateChangeListener listener = listeners.keyAt(i); 1173 listeners.valueAt(i).post(() -> 1174 listener.onTouchExplorationStateChanged(isTouchExplorationEnabled)); 1175 } 1176 } 1177 1178 /** 1179 * Notifies the registered {@link HighTextContrastChangeListener}s. 1180 */ notifyHighTextContrastStateChanged()1181 private void notifyHighTextContrastStateChanged() { 1182 final boolean isHighTextContrastEnabled; 1183 final ArrayMap<HighTextContrastChangeListener, Handler> listeners; 1184 synchronized (mLock) { 1185 if (mHighTextContrastStateChangeListeners.isEmpty()) { 1186 return; 1187 } 1188 isHighTextContrastEnabled = mIsHighTextContrastEnabled; 1189 listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners); 1190 } 1191 1192 final int numListeners = listeners.size(); 1193 for (int i = 0; i < numListeners; i++) { 1194 final HighTextContrastChangeListener listener = listeners.keyAt(i); 1195 listeners.valueAt(i).post(() -> 1196 listener.onHighTextContrastStateChanged(isHighTextContrastEnabled)); 1197 } 1198 } 1199 1200 /** 1201 * Determines if the accessibility button within the system navigation area is supported. 1202 * 1203 * @return {@code true} if the accessibility button is supported on this device, 1204 * {@code false} otherwise 1205 */ isAccessibilityButtonSupported()1206 public static boolean isAccessibilityButtonSupported() { 1207 final Resources res = Resources.getSystem(); 1208 return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); 1209 } 1210 1211 private final class MyCallback implements Handler.Callback { 1212 public static final int MSG_SET_STATE = 1; 1213 1214 @Override handleMessage(Message message)1215 public boolean handleMessage(Message message) { 1216 switch (message.what) { 1217 case MSG_SET_STATE: { 1218 // See comment at mClient 1219 final int state = message.arg1; 1220 synchronized (mLock) { 1221 setStateLocked(state); 1222 } 1223 } break; 1224 } 1225 return true; 1226 } 1227 } 1228 } 1229