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