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