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.content.Context; 22 import android.content.pm.PackageManager; 23 import android.content.pm.ServiceInfo; 24 import android.os.Binder; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.Looper; 28 import android.os.Message; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.SystemClock; 33 import android.os.UserHandle; 34 import android.util.Log; 35 import android.view.IWindow; 36 import android.view.View; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import 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 */ 67 public 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 static final Object sInstanceSync = new Object(); 79 80 private static AccessibilityManager sInstance; 81 82 private static final int DO_SET_STATE = 10; 83 84 final IAccessibilityManager mService; 85 86 final int mUserId; 87 88 final Handler mHandler; 89 90 boolean mIsEnabled; 91 92 boolean mIsTouchExplorationEnabled; 93 94 private final CopyOnWriteArrayList<AccessibilityStateChangeListener> 95 mAccessibilityStateChangeListeners = new CopyOnWriteArrayList< 96 AccessibilityStateChangeListener>(); 97 98 private final CopyOnWriteArrayList<TouchExplorationStateChangeListener> 99 mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList< 100 TouchExplorationStateChangeListener>(); 101 102 /** 103 * Listener for the system accessibility state. To listen for changes to the 104 * accessibility state on the device, implement this interface and register 105 * it with the system by calling {@link #addAccessibilityStateChangeListener}. 106 */ 107 public interface AccessibilityStateChangeListener { 108 109 /** 110 * Called when the accessibility enabled state changes. 111 * 112 * @param enabled Whether accessibility is enabled. 113 */ 114 public void onAccessibilityStateChanged(boolean enabled); 115 } 116 117 /** 118 * Listener for the system touch exploration state. To listen for changes to 119 * the touch exploration state on the device, implement this interface and 120 * register it with the system by calling 121 * {@link #addTouchExplorationStateChangeListener}. 122 */ 123 public interface TouchExplorationStateChangeListener { 124 125 /** 126 * Called when the touch exploration enabled state changes. 127 * 128 * @param enabled Whether touch exploration is enabled. 129 */ 130 public void onTouchExplorationStateChanged(boolean enabled); 131 } 132 133 final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { 134 public void setState(int state) { 135 mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget(); 136 } 137 }; 138 139 class MyHandler extends Handler { 140 141 MyHandler(Looper mainLooper) { 142 super(mainLooper); 143 } 144 145 @Override 146 public void handleMessage(Message message) { 147 switch (message.what) { 148 case DO_SET_STATE : 149 setState(message.arg1); 150 return; 151 default : 152 Log.w(LOG_TAG, "Unknown message type: " + message.what); 153 } 154 } 155 } 156 157 /** 158 * Get an AccessibilityManager instance (create one if necessary). 159 * 160 * @param context Context in which this manager operates. 161 * 162 * @hide 163 */ 164 public static AccessibilityManager getInstance(Context context) { 165 synchronized (sInstanceSync) { 166 if (sInstance == null) { 167 final int userId; 168 if (Binder.getCallingUid() == Process.SYSTEM_UID 169 || context.checkCallingOrSelfPermission( 170 Manifest.permission.INTERACT_ACROSS_USERS) 171 == PackageManager.PERMISSION_GRANTED 172 || context.checkCallingOrSelfPermission( 173 Manifest.permission.INTERACT_ACROSS_USERS_FULL) 174 == PackageManager.PERMISSION_GRANTED) { 175 userId = UserHandle.USER_CURRENT; 176 } else { 177 userId = UserHandle.myUserId(); 178 } 179 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); 180 IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); 181 sInstance = new AccessibilityManager(context, service, userId); 182 } 183 } 184 return sInstance; 185 } 186 187 /** 188 * Create an instance. 189 * 190 * @param context A {@link Context}. 191 * @param service An interface to the backing service. 192 * @param userId User id under which to run. 193 * 194 * @hide 195 */ 196 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { 197 mHandler = new MyHandler(context.getMainLooper()); 198 mService = service; 199 mUserId = userId; 200 201 try { 202 final int stateFlags = mService.addClient(mClient, userId); 203 setState(stateFlags); 204 } catch (RemoteException re) { 205 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); 206 } 207 } 208 209 /** 210 * Returns if the accessibility in the system is enabled. 211 * 212 * @return True if accessibility is enabled, false otherwise. 213 */ 214 public boolean isEnabled() { 215 synchronized (mHandler) { 216 return mIsEnabled; 217 } 218 } 219 220 /** 221 * Returns if the touch exploration in the system is enabled. 222 * 223 * @return True if touch exploration is enabled, false otherwise. 224 */ 225 public boolean isTouchExplorationEnabled() { 226 synchronized (mHandler) { 227 return mIsTouchExplorationEnabled; 228 } 229 } 230 231 /** 232 * Returns the client interface this instance registers in 233 * the centralized accessibility manager service. 234 * 235 * @return The client. 236 * 237 * @hide 238 */ 239 public IAccessibilityManagerClient getClient() { 240 return (IAccessibilityManagerClient) mClient.asBinder(); 241 } 242 243 /** 244 * Sends an {@link AccessibilityEvent}. 245 * 246 * @param event The event to send. 247 * 248 * @throws IllegalStateException if accessibility is not enabled. 249 * 250 * <strong>Note:</strong> The preferred mechanism for sending custom accessibility 251 * events is through calling 252 * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} 253 * instead of this method to allow predecessors to augment/filter events sent by 254 * their descendants. 255 */ 256 public void sendAccessibilityEvent(AccessibilityEvent event) { 257 if (!mIsEnabled) { 258 throw new IllegalStateException("Accessibility off. Did you forget to check that?"); 259 } 260 boolean doRecycle = false; 261 try { 262 event.setEventTime(SystemClock.uptimeMillis()); 263 // it is possible that this manager is in the same process as the service but 264 // client using it is called through Binder from another process. Example: MMS 265 // app adds a SMS notification and the NotificationManagerService calls this method 266 long identityToken = Binder.clearCallingIdentity(); 267 doRecycle = mService.sendAccessibilityEvent(event, mUserId); 268 Binder.restoreCallingIdentity(identityToken); 269 if (DEBUG) { 270 Log.i(LOG_TAG, event + " sent"); 271 } 272 } catch (RemoteException re) { 273 Log.e(LOG_TAG, "Error during sending " + event + " ", re); 274 } finally { 275 if (doRecycle) { 276 event.recycle(); 277 } 278 } 279 } 280 281 /** 282 * Requests feedback interruption from all accessibility services. 283 */ 284 public void interrupt() { 285 if (!mIsEnabled) { 286 throw new IllegalStateException("Accessibility off. Did you forget to check that?"); 287 } 288 try { 289 mService.interrupt(mUserId); 290 if (DEBUG) { 291 Log.i(LOG_TAG, "Requested interrupt from all services"); 292 } 293 } catch (RemoteException re) { 294 Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); 295 } 296 } 297 298 /** 299 * Returns the {@link ServiceInfo}s of the installed accessibility services. 300 * 301 * @return An unmodifiable list with {@link ServiceInfo}s. 302 * 303 * @deprecated Use {@link #getInstalledAccessibilityServiceList()} 304 */ 305 @Deprecated 306 public List<ServiceInfo> getAccessibilityServiceList() { 307 List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); 308 List<ServiceInfo> services = new ArrayList<ServiceInfo>(); 309 final int infoCount = infos.size(); 310 for (int i = 0; i < infoCount; i++) { 311 AccessibilityServiceInfo info = infos.get(i); 312 services.add(info.getResolveInfo().serviceInfo); 313 } 314 return Collections.unmodifiableList(services); 315 } 316 317 /** 318 * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. 319 * 320 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 321 */ 322 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { 323 List<AccessibilityServiceInfo> services = null; 324 try { 325 services = mService.getInstalledAccessibilityServiceList(mUserId); 326 if (DEBUG) { 327 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 328 } 329 } catch (RemoteException re) { 330 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 331 } 332 return Collections.unmodifiableList(services); 333 } 334 335 /** 336 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services 337 * for a given feedback type. 338 * 339 * @param feedbackTypeFlags The feedback type flags. 340 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 341 * 342 * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE 343 * @see AccessibilityServiceInfo#FEEDBACK_GENERIC 344 * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC 345 * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN 346 * @see AccessibilityServiceInfo#FEEDBACK_VISUAL 347 */ 348 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 349 int feedbackTypeFlags) { 350 List<AccessibilityServiceInfo> services = null; 351 try { 352 services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId); 353 if (DEBUG) { 354 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 355 } 356 } catch (RemoteException re) { 357 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 358 } 359 return Collections.unmodifiableList(services); 360 } 361 362 /** 363 * Registers an {@link AccessibilityStateChangeListener} for changes in 364 * the global accessibility state of the system. 365 * 366 * @param listener The listener. 367 * @return True if successfully registered. 368 */ 369 public boolean addAccessibilityStateChangeListener( 370 AccessibilityStateChangeListener listener) { 371 return mAccessibilityStateChangeListeners.add(listener); 372 } 373 374 /** 375 * Unregisters an {@link AccessibilityStateChangeListener}. 376 * 377 * @param listener The listener. 378 * @return True if successfully unregistered. 379 */ 380 public boolean removeAccessibilityStateChangeListener( 381 AccessibilityStateChangeListener listener) { 382 return mAccessibilityStateChangeListeners.remove(listener); 383 } 384 385 /** 386 * Registers a {@link TouchExplorationStateChangeListener} for changes in 387 * the global touch exploration state of the system. 388 * 389 * @param listener The listener. 390 * @return True if successfully registered. 391 */ 392 public boolean addTouchExplorationStateChangeListener( 393 TouchExplorationStateChangeListener listener) { 394 return mTouchExplorationStateChangeListeners.add(listener); 395 } 396 397 /** 398 * Unregisters a {@link TouchExplorationStateChangeListener}. 399 * 400 * @param listener The listener. 401 * @return True if successfully unregistered. 402 */ 403 public boolean removeTouchExplorationStateChangeListener( 404 TouchExplorationStateChangeListener listener) { 405 return mTouchExplorationStateChangeListeners.remove(listener); 406 } 407 408 /** 409 * Sets the current state and notifies listeners, if necessary. 410 * 411 * @param stateFlags The state flags. 412 */ 413 private void setState(int stateFlags) { 414 final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; 415 final boolean touchExplorationEnabled = 416 (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; 417 synchronized (mHandler) { 418 final boolean wasEnabled = mIsEnabled; 419 final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; 420 421 // Ensure listeners get current state from isZzzEnabled() calls. 422 mIsEnabled = enabled; 423 mIsTouchExplorationEnabled = touchExplorationEnabled; 424 425 if (wasEnabled != enabled) { 426 notifyAccessibilityStateChangedLh(); 427 } 428 429 if (wasTouchExplorationEnabled != touchExplorationEnabled) { 430 notifyTouchExplorationStateChangedLh(); 431 } 432 } 433 } 434 435 /** 436 * Notifies the registered {@link AccessibilityStateChangeListener}s. 437 * <p> 438 * The caller must be locked on {@link #mHandler}. 439 */ 440 private void notifyAccessibilityStateChangedLh() { 441 final int listenerCount = mAccessibilityStateChangeListeners.size(); 442 for (int i = 0; i < listenerCount; i++) { 443 mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled); 444 } 445 } 446 447 /** 448 * Notifies the registered {@link TouchExplorationStateChangeListener}s. 449 * <p> 450 * The caller must be locked on {@link #mHandler}. 451 */ 452 private void notifyTouchExplorationStateChangedLh() { 453 final int listenerCount = mTouchExplorationStateChangeListeners.size(); 454 for (int i = 0; i < listenerCount; i++) { 455 mTouchExplorationStateChangeListeners.get(i) 456 .onTouchExplorationStateChanged(mIsTouchExplorationEnabled); 457 } 458 } 459 460 /** 461 * Adds an accessibility interaction connection interface for a given window. 462 * @param windowToken The window token to which a connection is added. 463 * @param connection The connection. 464 * 465 * @hide 466 */ 467 public int addAccessibilityInteractionConnection(IWindow windowToken, 468 IAccessibilityInteractionConnection connection) { 469 try { 470 return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId); 471 } catch (RemoteException re) { 472 Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); 473 } 474 return View.NO_ID; 475 } 476 477 /** 478 * Removed an accessibility interaction connection interface for a given window. 479 * @param windowToken The window token to which a connection is removed. 480 * 481 * @hide 482 */ 483 public void removeAccessibilityInteractionConnection(IWindow windowToken) { 484 try { 485 mService.removeAccessibilityInteractionConnection(windowToken); 486 } catch (RemoteException re) { 487 Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); 488 } 489 } 490 } 491