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.accessibilityservice.AccessibilityServiceInfo; 20 import android.content.Context; 21 import android.content.pm.ServiceInfo; 22 import android.os.Binder; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.os.SystemClock; 30 import android.os.UserHandle; 31 import android.util.Log; 32 import android.view.IWindow; 33 import android.view.View; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.concurrent.CopyOnWriteArrayList; 39 40 /** 41 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, 42 * and provides facilities for querying the accessibility state of the system. 43 * Accessibility events are generated when something notable happens in the user interface, 44 * for example an {@link android.app.Activity} starts, the focus or selection of a 45 * {@link android.view.View} changes etc. Parties interested in handling accessibility 46 * events implement and register an accessibility service which extends 47 * {@link android.accessibilityservice.AccessibilityService}. 48 * <p> 49 * To obtain a handle to the accessibility manager do the following: 50 * </p> 51 * <p> 52 * <code> 53 * <pre>AccessibilityManager accessibilityManager = 54 * (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre> 55 * </code> 56 * </p> 57 * 58 * @see AccessibilityEvent 59 * @see AccessibilityNodeInfo 60 * @see android.accessibilityservice.AccessibilityService 61 * @see Context#getSystemService 62 * @see Context#ACCESSIBILITY_SERVICE 63 */ 64 public final class AccessibilityManager { 65 private static final boolean DEBUG = false; 66 67 private static final String LOG_TAG = "AccessibilityManager"; 68 69 /** @hide */ 70 public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; 71 72 /** @hide */ 73 public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; 74 75 static final Object sInstanceSync = new Object(); 76 77 private static AccessibilityManager sInstance; 78 79 private static final int DO_SET_STATE = 10; 80 81 final IAccessibilityManager mService; 82 83 final int mUserId; 84 85 final Handler mHandler; 86 87 boolean mIsEnabled; 88 89 boolean mIsTouchExplorationEnabled; 90 91 final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners = 92 new CopyOnWriteArrayList<AccessibilityStateChangeListener>(); 93 94 /** 95 * Listener for the system accessibility state. To listen for changes to the accessibility 96 * state on the device, implement this interface and register it with the system by 97 * calling {@link AccessibilityManager#addAccessibilityStateChangeListener 98 * addAccessibilityStateChangeListener()}. 99 */ 100 public interface AccessibilityStateChangeListener { 101 102 /** 103 * Called back on change in the accessibility state. 104 * 105 * @param enabled Whether accessibility is enabled. 106 */ 107 public void onAccessibilityStateChanged(boolean enabled); 108 } 109 110 final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { 111 public void setState(int state) { 112 mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget(); 113 } 114 }; 115 116 class MyHandler extends Handler { 117 118 MyHandler(Looper mainLooper) { 119 super(mainLooper); 120 } 121 122 @Override 123 public void handleMessage(Message message) { 124 switch (message.what) { 125 case DO_SET_STATE : 126 setState(message.arg1); 127 return; 128 default : 129 Log.w(LOG_TAG, "Unknown message type: " + message.what); 130 } 131 } 132 } 133 134 /** 135 * Creates the singleton AccessibilityManager to be shared across users. This 136 * has to be called before the local AccessibilityManager is created to ensure 137 * it registers itself in the system correctly. 138 * <p> 139 * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or 140 * INTERACT_ACROSS_USERS permission. 141 * </p> 142 * @param context Context in which this manager operates. 143 * @throws IllegalStateException if not called before the local 144 * AccessibilityManager is instantiated. 145 * 146 * @hide 147 */ 148 public static void createAsSharedAcrossUsers(Context context) { 149 synchronized (sInstanceSync) { 150 if (sInstance != null) { 151 throw new IllegalStateException("AccessibilityManager already created."); 152 } 153 createSingletonInstance(context, UserHandle.USER_CURRENT); 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 createSingletonInstance(context, UserHandle.myUserId()); 168 } 169 } 170 return sInstance; 171 } 172 173 /** 174 * Creates the singleton instance. 175 * 176 * @param context Context in which this manager operates. 177 * @param userId The user id under which to operate. 178 */ 179 private static void createSingletonInstance(Context context, int userId) { 180 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); 181 IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); 182 sInstance = new AccessibilityManager(context, service, userId); 183 } 184 185 /** 186 * Create an instance. 187 * 188 * @param context A {@link Context}. 189 * @param service An interface to the backing service. 190 * @param userId User id under which to run. 191 * 192 * @hide 193 */ 194 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { 195 mHandler = new MyHandler(context.getMainLooper()); 196 mService = service; 197 mUserId = userId; 198 199 try { 200 final int stateFlags = mService.addClient(mClient, userId); 201 setState(stateFlags); 202 } catch (RemoteException re) { 203 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); 204 } 205 } 206 207 /** 208 * Returns if the accessibility in the system is enabled. 209 * 210 * @return True if accessibility is enabled, false otherwise. 211 */ 212 public boolean isEnabled() { 213 synchronized (mHandler) { 214 return mIsEnabled; 215 } 216 } 217 218 /** 219 * Returns if the touch exploration in the system is enabled. 220 * 221 * @return True if touch exploration is enabled, false otherwise. 222 */ 223 public boolean isTouchExplorationEnabled() { 224 synchronized (mHandler) { 225 return mIsTouchExplorationEnabled; 226 } 227 } 228 229 /** 230 * Returns the client interface this instance registers in 231 * the centralized accessibility manager service. 232 * 233 * @return The client. 234 * 235 * @hide 236 */ 237 public IAccessibilityManagerClient getClient() { 238 return (IAccessibilityManagerClient) mClient.asBinder(); 239 } 240 241 /** 242 * Sends an {@link AccessibilityEvent}. 243 * 244 * @param event The event to send. 245 * 246 * @throws IllegalStateException if accessibility is not enabled. 247 * 248 * <strong>Note:</strong> The preferred mechanism for sending custom accessibility 249 * events is through calling 250 * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} 251 * instead of this method to allow predecessors to augment/filter events sent by 252 * their descendants. 253 */ 254 public void sendAccessibilityEvent(AccessibilityEvent event) { 255 if (!mIsEnabled) { 256 throw new IllegalStateException("Accessibility off. Did you forget to check that?"); 257 } 258 boolean doRecycle = false; 259 try { 260 event.setEventTime(SystemClock.uptimeMillis()); 261 // it is possible that this manager is in the same process as the service but 262 // client using it is called through Binder from another process. Example: MMS 263 // app adds a SMS notification and the NotificationManagerService calls this method 264 long identityToken = Binder.clearCallingIdentity(); 265 doRecycle = mService.sendAccessibilityEvent(event, mUserId); 266 Binder.restoreCallingIdentity(identityToken); 267 if (DEBUG) { 268 Log.i(LOG_TAG, event + " sent"); 269 } 270 } catch (RemoteException re) { 271 Log.e(LOG_TAG, "Error during sending " + event + " ", re); 272 } finally { 273 if (doRecycle) { 274 event.recycle(); 275 } 276 } 277 } 278 279 /** 280 * Requests feedback interruption from all accessibility services. 281 */ 282 public void interrupt() { 283 if (!mIsEnabled) { 284 throw new IllegalStateException("Accessibility off. Did you forget to check that?"); 285 } 286 try { 287 mService.interrupt(mUserId); 288 if (DEBUG) { 289 Log.i(LOG_TAG, "Requested interrupt from all services"); 290 } 291 } catch (RemoteException re) { 292 Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); 293 } 294 } 295 296 /** 297 * Returns the {@link ServiceInfo}s of the installed accessibility services. 298 * 299 * @return An unmodifiable list with {@link ServiceInfo}s. 300 * 301 * @deprecated Use {@link #getInstalledAccessibilityServiceList()} 302 */ 303 @Deprecated 304 public List<ServiceInfo> getAccessibilityServiceList() { 305 List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); 306 List<ServiceInfo> services = new ArrayList<ServiceInfo>(); 307 final int infoCount = infos.size(); 308 for (int i = 0; i < infoCount; i++) { 309 AccessibilityServiceInfo info = infos.get(i); 310 services.add(info.getResolveInfo().serviceInfo); 311 } 312 return Collections.unmodifiableList(services); 313 } 314 315 /** 316 * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. 317 * 318 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 319 */ 320 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { 321 List<AccessibilityServiceInfo> services = null; 322 try { 323 services = mService.getInstalledAccessibilityServiceList(mUserId); 324 if (DEBUG) { 325 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 326 } 327 } catch (RemoteException re) { 328 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 329 } 330 return Collections.unmodifiableList(services); 331 } 332 333 /** 334 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services 335 * for a given feedback type. 336 * 337 * @param feedbackTypeFlags The feedback type flags. 338 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. 339 * 340 * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE 341 * @see AccessibilityServiceInfo#FEEDBACK_GENERIC 342 * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC 343 * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN 344 * @see AccessibilityServiceInfo#FEEDBACK_VISUAL 345 */ 346 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 347 int feedbackTypeFlags) { 348 List<AccessibilityServiceInfo> services = null; 349 try { 350 services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId); 351 if (DEBUG) { 352 Log.i(LOG_TAG, "Installed AccessibilityServices " + services); 353 } 354 } catch (RemoteException re) { 355 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); 356 } 357 return Collections.unmodifiableList(services); 358 } 359 360 /** 361 * Registers an {@link AccessibilityStateChangeListener} for changes in 362 * the global accessibility state of the system. 363 * 364 * @param listener The listener. 365 * @return True if successfully registered. 366 */ 367 public boolean addAccessibilityStateChangeListener( 368 AccessibilityStateChangeListener listener) { 369 return mAccessibilityStateChangeListeners.add(listener); 370 } 371 372 /** 373 * Unregisters an {@link AccessibilityStateChangeListener}. 374 * 375 * @param listener The listener. 376 * @return True if successfully unregistered. 377 */ 378 public boolean removeAccessibilityStateChangeListener( 379 AccessibilityStateChangeListener listener) { 380 return mAccessibilityStateChangeListeners.remove(listener); 381 } 382 383 /** 384 * Sets the current state. 385 * 386 * @param stateFlags The state flags. 387 */ 388 private void setState(int stateFlags) { 389 final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; 390 setAccessibilityState(accessibilityEnabled); 391 mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; 392 } 393 394 /** 395 * Sets the enabled state. 396 * 397 * @param isEnabled The accessibility state. 398 */ 399 private void setAccessibilityState(boolean isEnabled) { 400 synchronized (mHandler) { 401 if (isEnabled != mIsEnabled) { 402 mIsEnabled = isEnabled; 403 notifyAccessibilityStateChanged(); 404 } 405 } 406 } 407 408 /** 409 * Notifies the registered {@link AccessibilityStateChangeListener}s. 410 */ 411 private void notifyAccessibilityStateChanged() { 412 final int listenerCount = mAccessibilityStateChangeListeners.size(); 413 for (int i = 0; i < listenerCount; i++) { 414 mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled); 415 } 416 } 417 418 /** 419 * Adds an accessibility interaction connection interface for a given window. 420 * @param windowToken The window token to which a connection is added. 421 * @param connection The connection. 422 * 423 * @hide 424 */ 425 public int addAccessibilityInteractionConnection(IWindow windowToken, 426 IAccessibilityInteractionConnection connection) { 427 try { 428 return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId); 429 } catch (RemoteException re) { 430 Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); 431 } 432 return View.NO_ID; 433 } 434 435 /** 436 * Removed an accessibility interaction connection interface for a given window. 437 * @param windowToken The window token to which a connection is removed. 438 * 439 * @hide 440 */ 441 public void removeAccessibilityInteractionConnection(IWindow windowToken) { 442 try { 443 mService.removeAccessibilityInteractionConnection(windowToken); 444 } catch (RemoteException re) { 445 Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); 446 } 447 } 448 } 449