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