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