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 android.Manifest; 20 import android.accessibilityservice.AccessibilityServiceInfo; 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ServiceInfo; 25 import android.os.Binder; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.Process; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.SystemClock; 34 import android.os.UserHandle; 35 import android.util.Log; 36 import android.view.IWindow; 37 import android.view.View; 38 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.concurrent.CopyOnWriteArrayList; 43 44 /** 45 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, 46 * and provides facilities for querying the accessibility state of the system. 47 * Accessibility events are generated when something notable happens in the user interface, 48 * for example an {@link android.app.Activity} starts, the focus or selection of a 49 * {@link android.view.View} changes etc. Parties interested in handling accessibility 50 * events implement and register an accessibility service which extends 51 * {@link android.accessibilityservice.AccessibilityService}. 52 * <p> 53 * To obtain a handle to the accessibility manager do the following: 54 * </p> 55 * <p> 56 * <code> 57 * <pre>AccessibilityManager accessibilityManager = 58 * (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre> 59 * </code> 60 * </p> 61 * 62 * @see AccessibilityEvent 63 * @see AccessibilityNodeInfo 64 * @see android.accessibilityservice.AccessibilityService 65 * @see Context#getSystemService 66 * @see Context#ACCESSIBILITY_SERVICE 67 */ 68 public final class AccessibilityManager { 69 private static final boolean DEBUG = false; 70 71 private static final String LOG_TAG = "AccessibilityManager"; 72 73 /** @hide */ 74 public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; 75 76 /** @hide */ 77 public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; 78 79 /** @hide */ 80 public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004; 81 82 /** @hide */ 83 public static final int DALTONIZER_DISABLED = -1; 84 85 /** @hide */ 86 public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; 87 88 /** @hide */ 89 public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; 90 91 static final Object sInstanceSync = new Object(); 92 93 private static AccessibilityManager sInstance; 94 95 private final Object mLock = new Object(); 96 97 private IAccessibilityManager mService; 98 99 final int mUserId; 100 101 final Handler mHandler; 102 103 boolean mIsEnabled; 104 105 boolean mIsTouchExplorationEnabled; 106 107 boolean mIsHighTextContrastEnabled; 108 109 private final CopyOnWriteArrayList<AccessibilityStateChangeListener> 110 mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>(); 111 112 private final CopyOnWriteArrayList<TouchExplorationStateChangeListener> 113 mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<>(); 114 115 private final CopyOnWriteArrayList<HighTextContrastChangeListener> 116 mHighTextContrastStateChangeListeners = new CopyOnWriteArrayList<>(); 117 118 /** 119 * Listener for the system accessibility state. To listen for changes to the 120 * accessibility state on the device, implement this interface and register 121 * it with the system by calling {@link #addAccessibilityStateChangeListener}. 122 */ 123 public interface AccessibilityStateChangeListener { 124 125 /** 126 * Called when the accessibility enabled state changes. 127 * 128 * @param enabled Whether accessibility is enabled. 129 */ onAccessibilityStateChanged(boolean enabled)130 public void onAccessibilityStateChanged(boolean enabled); 131 } 132 133 /** 134 * Listener for the system touch exploration state. To listen for changes to 135 * the touch exploration state on the device, implement this interface and 136 * register it with the system by calling 137 * {@link #addTouchExplorationStateChangeListener}. 138 */ 139 public interface TouchExplorationStateChangeListener { 140 141 /** 142 * Called when the touch exploration enabled state changes. 143 * 144 * @param enabled Whether touch exploration is enabled. 145 */ onTouchExplorationStateChanged(boolean enabled)146 public void onTouchExplorationStateChanged(boolean enabled); 147 } 148 149 /** 150 * Listener for the system high text contrast state. To listen for changes to 151 * the high text contrast state on the device, implement this interface and 152 * register it with the system by calling 153 * {@link #addHighTextContrastStateChangeListener}. 154 * 155 * @hide 156 */ 157 public interface HighTextContrastChangeListener { 158 159 /** 160 * Called when the high text contrast enabled state changes. 161 * 162 * @param enabled Whether high text contrast is enabled. 163 */ onHighTextContrastStateChanged(boolean enabled)164 public void onHighTextContrastStateChanged(boolean enabled); 165 } 166 167 private final IAccessibilityManagerClient.Stub mClient = 168 new IAccessibilityManagerClient.Stub() { 169 public void setState(int state) { 170 // We do not want to change this immediately as the applicatoin may 171 // have already checked that accessibility is on and fired an event, 172 // that is now propagating up the view tree, Hence, if accessibility 173 // is now off an exception will be thrown. We want to have the exception 174 // enforcement to guard against apps that fire unnecessary accessibility 175 // events when accessibility is off. 176 mHandler.obtainMessage(MyHandler.MSG_SET_STATE, state, 0).sendToTarget(); 177 } 178 }; 179 180 /** 181 * Get an AccessibilityManager instance (create one if necessary). 182 * 183 * @param context Context in which this manager operates. 184 * 185 * @hide 186 */ getInstance(Context context)187 public static AccessibilityManager getInstance(Context context) { 188 synchronized (sInstanceSync) { 189 if (sInstance == null) { 190 final int userId; 191 if (Binder.getCallingUid() == Process.SYSTEM_UID 192 || context.checkCallingOrSelfPermission( 193 Manifest.permission.INTERACT_ACROSS_USERS) 194 == PackageManager.PERMISSION_GRANTED 195 || context.checkCallingOrSelfPermission( 196 Manifest.permission.INTERACT_ACROSS_USERS_FULL) 197 == PackageManager.PERMISSION_GRANTED) { 198 userId = UserHandle.USER_CURRENT; 199 } else { 200 userId = UserHandle.myUserId(); 201 } 202 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); 203 IAccessibilityManager service = iBinder == null 204 ? null : IAccessibilityManager.Stub.asInterface(iBinder); 205 sInstance = new AccessibilityManager(context, service, userId); 206 } 207 } 208 return sInstance; 209 } 210 211 /** 212 * Create an instance. 213 * 214 * @param context A {@link Context}. 215 * @param service An interface to the backing service. 216 * @param userId User id under which to run. 217 * 218 * @hide 219 */ AccessibilityManager(Context context, IAccessibilityManager service, int userId)220 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { 221 mHandler = new MyHandler(context.getMainLooper()); 222 mService = service; 223 mUserId = userId; 224 synchronized (mLock) { 225 tryConnectToServiceLocked(); 226 } 227 } 228 229 /** 230 * @hide 231 */ getClient()232 public IAccessibilityManagerClient getClient() { 233 return mClient; 234 } 235 236 /** 237 * Returns if the accessibility in the system is enabled. 238 * 239 * @return True if accessibility is enabled, false otherwise. 240 */ isEnabled()241 public boolean isEnabled() { 242 synchronized (mLock) { 243 IAccessibilityManager service = getServiceLocked(); 244 if (service == null) { 245 return false; 246 } 247 return mIsEnabled; 248 } 249 } 250 251 /** 252 * Returns if the touch exploration in the system is enabled. 253 * 254 * @return True if touch exploration is enabled, false otherwise. 255 */ isTouchExplorationEnabled()256 public boolean isTouchExplorationEnabled() { 257 synchronized (mLock) { 258 IAccessibilityManager service = getServiceLocked(); 259 if (service == null) { 260 return false; 261 } 262 return mIsTouchExplorationEnabled; 263 } 264 } 265 266 /** 267 * Returns if the high text contrast in the system is enabled. 268 * <p> 269 * <strong>Note:</strong> You need to query this only if you application is 270 * doing its own rendering and does not rely on the platform rendering pipeline. 271 * </p> 272 * 273 * @return True if high text contrast is enabled, false otherwise. 274 * 275 * @hide 276 */ isHighTextContrastEnabled()277 public boolean isHighTextContrastEnabled() { 278 synchronized (mLock) { 279 IAccessibilityManager service = getServiceLocked(); 280 if (service == null) { 281 return false; 282 } 283 return mIsHighTextContrastEnabled; 284 } 285 } 286 287 /** 288 * Sends an {@link AccessibilityEvent}. 289 * 290 * @param event The event to send. 291 * 292 * @throws IllegalStateException if accessibility is not enabled. 293 * 294 * <strong>Note:</strong> The preferred mechanism for sending custom accessibility 295 * events is through calling 296 * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} 297 * instead of this method to allow predecessors to augment/filter events sent by 298 * their descendants. 299 */ sendAccessibilityEvent(AccessibilityEvent event)300 public void sendAccessibilityEvent(AccessibilityEvent event) { 301 final IAccessibilityManager service; 302 final int userId; 303 synchronized (mLock) { 304 service = getServiceLocked(); 305 if (service == null) { 306 return; 307 } 308 if (!mIsEnabled) { 309 throw new IllegalStateException("Accessibility off. Did you forget to check that?"); 310 } 311 userId = mUserId; 312 } 313 boolean doRecycle = false; 314 try { 315 event.setEventTime(SystemClock.uptimeMillis()); 316 // it is possible that this manager is in the same process as the service but 317 // client using it is called through Binder from another process. Example: MMS 318 // app adds a SMS notification and the NotificationManagerService calls this method 319 long identityToken = Binder.clearCallingIdentity(); 320 doRecycle = service.sendAccessibilityEvent(event, userId); 321 Binder.restoreCallingIdentity(identityToken); 322 if (DEBUG) { 323 Log.i(LOG_TAG, event + " sent"); 324 } 325 } catch (RemoteException re) { 326 Log.e(LOG_TAG, "Error during sending " + event + " ", re); 327 } finally { 328 if (doRecycle) { 329 event.recycle(); 330 } 331 } 332 } 333 334 /** 335 * Requests feedback interruption from all accessibility services. 336 */ interrupt()337 public void interrupt() { 338 final IAccessibilityManager service; 339 final int userId; 340 synchronized (mLock) { 341 service = getServiceLocked(); 342 if (service == null) { 343 return; 344 } 345 if (!mIsEnabled) { 346 throw new IllegalStateException("Accessibility off. Did you forget to check that?"); 347 } 348 userId = mUserId; 349 } 350 try { 351 service.interrupt(userId); 352 if (DEBUG) { 353 Log.i(LOG_TAG, "Requested interrupt from all services"); 354 } 355 } catch (RemoteException re) { 356 Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); 357 } 358 } 359 360 /** 361 * Returns the {@link ServiceInfo}s of the installed accessibility services. 362 * 363 * @return An unmodifiable list with {@link ServiceInfo}s. 364 * 365 * @deprecated Use {@link #getInstalledAccessibilityServiceList()} 366 */ 367 @Deprecated getAccessibilityServiceList()368 public List<ServiceInfo> getAccessibilityServiceList() { 369 List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); 370 List<ServiceInfo> services = new ArrayList<>(); 371 final int infoCount = infos.size(); 372 for (int i = 0; i < infoCount; i++) { 373 AccessibilityServiceInfo info = infos.get(i); 374 services.add(info.getResolveInfo().serviceInfo); 375 } 376 return Collections.unmodifiableList(services); 377 } 378 379 /** 380 * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. 381 * 382 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 383 */ getInstalledAccessibilityServiceList()384 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { 385 final IAccessibilityManager service; 386 final int userId; 387 synchronized (mLock) { 388 service = getServiceLocked(); 389 if (service == null) { 390 return Collections.emptyList(); 391 } 392 userId = mUserId; 393 } 394 395 List<AccessibilityServiceInfo> services = null; 396 try { 397 services = service.getInstalledAccessibilityServiceList(userId); 398 if (DEBUG) { 399 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 400 } 401 } catch (RemoteException re) { 402 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 403 } 404 if (services != null) { 405 return Collections.unmodifiableList(services); 406 } else { 407 return Collections.emptyList(); 408 } 409 } 410 411 /** 412 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services 413 * for a given feedback type. 414 * 415 * @param feedbackTypeFlags The feedback type flags. 416 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 417 * 418 * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE 419 * @see AccessibilityServiceInfo#FEEDBACK_GENERIC 420 * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC 421 * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN 422 * @see AccessibilityServiceInfo#FEEDBACK_VISUAL 423 * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE 424 */ getEnabledAccessibilityServiceList( int feedbackTypeFlags)425 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 426 int feedbackTypeFlags) { 427 final IAccessibilityManager service; 428 final int userId; 429 synchronized (mLock) { 430 service = getServiceLocked(); 431 if (service == null) { 432 return Collections.emptyList(); 433 } 434 userId = mUserId; 435 } 436 437 List<AccessibilityServiceInfo> services = null; 438 try { 439 services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); 440 if (DEBUG) { 441 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 442 } 443 } catch (RemoteException re) { 444 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 445 } 446 if (services != null) { 447 return Collections.unmodifiableList(services); 448 } else { 449 return Collections.emptyList(); 450 } 451 } 452 453 /** 454 * Registers an {@link AccessibilityStateChangeListener} for changes in 455 * the global accessibility state of the system. 456 * 457 * @param listener The listener. 458 * @return True if successfully registered. 459 */ addAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)460 public boolean addAccessibilityStateChangeListener( 461 @NonNull AccessibilityStateChangeListener listener) { 462 // Final CopyOnArrayList - no lock needed. 463 return mAccessibilityStateChangeListeners.add(listener); 464 } 465 466 /** 467 * Unregisters an {@link AccessibilityStateChangeListener}. 468 * 469 * @param listener The listener. 470 * @return True if successfully unregistered. 471 */ removeAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)472 public boolean removeAccessibilityStateChangeListener( 473 @NonNull AccessibilityStateChangeListener listener) { 474 // Final CopyOnArrayList - no lock needed. 475 return mAccessibilityStateChangeListeners.remove(listener); 476 } 477 478 /** 479 * Registers a {@link TouchExplorationStateChangeListener} for changes in 480 * the global touch exploration state of the system. 481 * 482 * @param listener The listener. 483 * @return True if successfully registered. 484 */ addTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)485 public boolean addTouchExplorationStateChangeListener( 486 @NonNull TouchExplorationStateChangeListener listener) { 487 // Final CopyOnArrayList - no lock needed. 488 return mTouchExplorationStateChangeListeners.add(listener); 489 } 490 491 /** 492 * Unregisters a {@link TouchExplorationStateChangeListener}. 493 * 494 * @param listener The listener. 495 * @return True if successfully unregistered. 496 */ removeTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)497 public boolean removeTouchExplorationStateChangeListener( 498 @NonNull TouchExplorationStateChangeListener listener) { 499 // Final CopyOnArrayList - no lock needed. 500 return mTouchExplorationStateChangeListeners.remove(listener); 501 } 502 503 /** 504 * Registers a {@link HighTextContrastChangeListener} for changes in 505 * the global high text contrast state of the system. 506 * 507 * @param listener The listener. 508 * @return True if successfully registered. 509 * 510 * @hide 511 */ addHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener)512 public boolean addHighTextContrastStateChangeListener( 513 @NonNull HighTextContrastChangeListener listener) { 514 // Final CopyOnArrayList - no lock needed. 515 return mHighTextContrastStateChangeListeners.add(listener); 516 } 517 518 /** 519 * Unregisters a {@link HighTextContrastChangeListener}. 520 * 521 * @param listener The listener. 522 * @return True if successfully unregistered. 523 * 524 * @hide 525 */ removeHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener)526 public boolean removeHighTextContrastStateChangeListener( 527 @NonNull HighTextContrastChangeListener listener) { 528 // Final CopyOnArrayList - no lock needed. 529 return mHighTextContrastStateChangeListeners.remove(listener); 530 } 531 532 /** 533 * Sets the current state and notifies listeners, if necessary. 534 * 535 * @param stateFlags The state flags. 536 */ setStateLocked(int stateFlags)537 private void setStateLocked(int stateFlags) { 538 final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; 539 final boolean touchExplorationEnabled = 540 (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; 541 final boolean highTextContrastEnabled = 542 (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; 543 544 final boolean wasEnabled = mIsEnabled; 545 final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; 546 final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; 547 548 // Ensure listeners get current state from isZzzEnabled() calls. 549 mIsEnabled = enabled; 550 mIsTouchExplorationEnabled = touchExplorationEnabled; 551 mIsHighTextContrastEnabled = highTextContrastEnabled; 552 553 if (wasEnabled != enabled) { 554 mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED); 555 } 556 557 if (wasTouchExplorationEnabled != touchExplorationEnabled) { 558 mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED); 559 } 560 561 if (wasHighTextContrastEnabled != highTextContrastEnabled) { 562 mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED); 563 } 564 } 565 566 /** 567 * Adds an accessibility interaction connection interface for a given window. 568 * @param windowToken The window token to which a connection is added. 569 * @param connection The connection. 570 * 571 * @hide 572 */ addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection)573 public int addAccessibilityInteractionConnection(IWindow windowToken, 574 IAccessibilityInteractionConnection connection) { 575 final IAccessibilityManager service; 576 final int userId; 577 synchronized (mLock) { 578 service = getServiceLocked(); 579 if (service == null) { 580 return View.NO_ID; 581 } 582 userId = mUserId; 583 } 584 try { 585 return service.addAccessibilityInteractionConnection(windowToken, connection, userId); 586 } catch (RemoteException re) { 587 Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); 588 } 589 return View.NO_ID; 590 } 591 592 /** 593 * Removed an accessibility interaction connection interface for a given window. 594 * @param windowToken The window token to which a connection is removed. 595 * 596 * @hide 597 */ removeAccessibilityInteractionConnection(IWindow windowToken)598 public void removeAccessibilityInteractionConnection(IWindow windowToken) { 599 final IAccessibilityManager service; 600 synchronized (mLock) { 601 service = getServiceLocked(); 602 if (service == null) { 603 return; 604 } 605 } 606 try { 607 service.removeAccessibilityInteractionConnection(windowToken); 608 } catch (RemoteException re) { 609 Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); 610 } 611 } 612 getServiceLocked()613 private IAccessibilityManager getServiceLocked() { 614 if (mService == null) { 615 tryConnectToServiceLocked(); 616 } 617 return mService; 618 } 619 tryConnectToServiceLocked()620 private void tryConnectToServiceLocked() { 621 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); 622 if (iBinder == null) { 623 return; 624 } 625 IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); 626 try { 627 final int stateFlags = service.addClient(mClient, mUserId); 628 setStateLocked(stateFlags); 629 mService = service; 630 } catch (RemoteException re) { 631 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); 632 } 633 } 634 635 /** 636 * Notifies the registered {@link AccessibilityStateChangeListener}s. 637 */ handleNotifyAccessibilityStateChanged()638 private void handleNotifyAccessibilityStateChanged() { 639 final boolean isEnabled; 640 synchronized (mLock) { 641 isEnabled = mIsEnabled; 642 } 643 final int listenerCount = mAccessibilityStateChangeListeners.size(); 644 for (int i = 0; i < listenerCount; i++) { 645 mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(isEnabled); 646 } 647 } 648 649 /** 650 * Notifies the registered {@link TouchExplorationStateChangeListener}s. 651 */ handleNotifyTouchExplorationStateChanged()652 private void handleNotifyTouchExplorationStateChanged() { 653 final boolean isTouchExplorationEnabled; 654 synchronized (mLock) { 655 isTouchExplorationEnabled = mIsTouchExplorationEnabled; 656 } 657 final int listenerCount = mTouchExplorationStateChangeListeners.size(); 658 for (int i = 0; i < listenerCount; i++) { 659 mTouchExplorationStateChangeListeners.get(i) 660 .onTouchExplorationStateChanged(isTouchExplorationEnabled); 661 } 662 } 663 664 /** 665 * Notifies the registered {@link HighTextContrastChangeListener}s. 666 */ handleNotifyHighTextContrastStateChanged()667 private void handleNotifyHighTextContrastStateChanged() { 668 final boolean isHighTextContrastEnabled; 669 synchronized (mLock) { 670 isHighTextContrastEnabled = mIsHighTextContrastEnabled; 671 } 672 final int listenerCount = mHighTextContrastStateChangeListeners.size(); 673 for (int i = 0; i < listenerCount; i++) { 674 mHighTextContrastStateChangeListeners.get(i) 675 .onHighTextContrastStateChanged(isHighTextContrastEnabled); 676 } 677 } 678 679 private final class MyHandler extends Handler { 680 public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1; 681 public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2; 682 public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3; 683 public static final int MSG_SET_STATE = 4; 684 MyHandler(Looper looper)685 public MyHandler(Looper looper) { 686 super(looper, null, false); 687 } 688 689 @Override handleMessage(Message message)690 public void handleMessage(Message message) { 691 switch (message.what) { 692 case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: { 693 handleNotifyAccessibilityStateChanged(); 694 } break; 695 696 case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: { 697 handleNotifyTouchExplorationStateChanged(); 698 } break; 699 700 case MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED: { 701 handleNotifyHighTextContrastStateChanged(); 702 } break; 703 704 case MSG_SET_STATE: { 705 // See comment at mClient 706 final int state = message.arg1; 707 synchronized (mLock) { 708 setStateLocked(state); 709 } 710 } break; 711 } 712 } 713 } 714 } 715