blob: 732699b0b0f41c52280e099a2a0383064ea77004 [file] [log] [blame]
svetoslavganov75986cf2009-05-14 22:28:01 -07001/*
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
Svetoslav Ganov736c2752011-04-22 18:30:36 -070019import android.accessibilityservice.AccessibilityServiceInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -070020import 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;
Svetoslav Ganov58d37b52012-09-18 12:04:19 -070030import android.os.UserHandle;
svetoslavganov75986cf2009-05-14 22:28:01 -070031import android.util.Log;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070032import android.view.IWindow;
33import android.view.View;
svetoslavganov75986cf2009-05-14 22:28:01 -070034
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -070035import java.util.ArrayList;
svetoslavganov75986cf2009-05-14 22:28:01 -070036import java.util.Collections;
37import java.util.List;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070038import java.util.concurrent.CopyOnWriteArrayList;
svetoslavganov75986cf2009-05-14 22:28:01 -070039
40/**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070041 * 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,
svetoslavganov75986cf2009-05-14 22:28:01 -070044 * 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}.
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070048 * <p>
49 * To obtain a handle to the accessibility manager do the following:
50 * </p>
51 * <p>
52 * <code>
Scott Mainb303d832011-10-12 16:45:18 -070053 * <pre>AccessibilityManager accessibilityManager =
54 * (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070055 * </code>
56 * </p>
svetoslavganov75986cf2009-05-14 22:28:01 -070057 *
58 * @see AccessibilityEvent
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070059 * @see AccessibilityNodeInfo
svetoslavganov75986cf2009-05-14 22:28:01 -070060 * @see android.accessibilityservice.AccessibilityService
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070061 * @see Context#getSystemService
62 * @see Context#ACCESSIBILITY_SERVICE
svetoslavganov75986cf2009-05-14 22:28:01 -070063 */
64public final class AccessibilityManager {
Svetoslav Ganov736c2752011-04-22 18:30:36 -070065 private static final boolean DEBUG = false;
66
svetoslavganov75986cf2009-05-14 22:28:01 -070067 private static final String LOG_TAG = "AccessibilityManager";
68
Svetoslav Ganov00aabf72011-07-21 11:35:03 -070069 /** @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
svetoslavganov75986cf2009-05-14 22:28:01 -070075 static final Object sInstanceSync = new Object();
76
77 private static AccessibilityManager sInstance;
78
Svetoslav Ganov00aabf72011-07-21 11:35:03 -070079 private static final int DO_SET_STATE = 10;
svetoslavganov75986cf2009-05-14 22:28:01 -070080
81 final IAccessibilityManager mService;
82
Svetoslav Ganov58d37b52012-09-18 12:04:19 -070083 final int mUserId;
84
svetoslavganov75986cf2009-05-14 22:28:01 -070085 final Handler mHandler;
86
87 boolean mIsEnabled;
88
Svetoslav Ganov35bfede2011-07-14 17:57:06 -070089 boolean mIsTouchExplorationEnabled;
90
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070091 final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners =
92 new CopyOnWriteArrayList<AccessibilityStateChangeListener>();
93
94 /**
Scott Mainb303d832011-10-12 16:45:18 -070095 * 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()}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070099 */
100 public interface AccessibilityStateChangeListener {
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700101
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700102 /**
103 * Called back on change in the accessibility state.
104 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700105 * @param enabled Whether accessibility is enabled.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700106 */
107 public void onAccessibilityStateChanged(boolean enabled);
108 }
109
svetoslavganov75986cf2009-05-14 22:28:01 -0700110 final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700111 public void setState(int state) {
112 mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget();
svetoslavganov75986cf2009-05-14 22:28:01 -0700113 }
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) {
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700125 case DO_SET_STATE :
126 setState(message.arg1);
svetoslavganov75986cf2009-05-14 22:28:01 -0700127 return;
128 default :
129 Log.w(LOG_TAG, "Unknown message type: " + message.what);
130 }
131 }
132 }
133
134 /**
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700135 * 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 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700158 * Get an AccessibilityManager instance (create one if necessary).
159 *
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700160 * @param context Context in which this manager operates.
161 *
svetoslavganov75986cf2009-05-14 22:28:01 -0700162 * @hide
163 */
164 public static AccessibilityManager getInstance(Context context) {
165 synchronized (sInstanceSync) {
166 if (sInstance == null) {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700167 createSingletonInstance(context, UserHandle.myUserId());
svetoslavganov75986cf2009-05-14 22:28:01 -0700168 }
169 }
170 return sInstance;
171 }
172
173 /**
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700174 * 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 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700186 * Create an instance.
187 *
188 * @param context A {@link Context}.
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700189 * @param service An interface to the backing service.
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700190 * @param userId User id under which to run.
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700191 *
192 * @hide
svetoslavganov75986cf2009-05-14 22:28:01 -0700193 */
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700194 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700195 mHandler = new MyHandler(context.getMainLooper());
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700196 mService = service;
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700197 mUserId = userId;
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700198
svetoslavganov75986cf2009-05-14 22:28:01 -0700199 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700200 final int stateFlags = mService.addClient(mClient, userId);
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700201 setState(stateFlags);
svetoslavganov75986cf2009-05-14 22:28:01 -0700202 } catch (RemoteException re) {
203 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
204 }
205 }
206
207 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700208 * Returns if the accessibility in the system is enabled.
svetoslavganov75986cf2009-05-14 22:28:01 -0700209 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700210 * @return True if accessibility is enabled, false otherwise.
svetoslavganov75986cf2009-05-14 22:28:01 -0700211 */
212 public boolean isEnabled() {
213 synchronized (mHandler) {
214 return mIsEnabled;
215 }
216 }
217
218 /**
Svetoslav Ganov35bfede2011-07-14 17:57:06 -0700219 * 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 /**
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700230 * 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() {
Jim Miller4dfecf52011-06-30 15:45:35 -0700238 return (IAccessibilityManagerClient) mClient.asBinder();
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700239 }
240
241 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700242 * Sends an {@link AccessibilityEvent}.
svetoslavganov75986cf2009-05-14 22:28:01 -0700243 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700244 * @param event The event to send.
svetoslavganov75986cf2009-05-14 22:28:01 -0700245 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700246 * @throws IllegalStateException if accessibility is not enabled.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700247 *
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.
svetoslavganov75986cf2009-05-14 22:28:01 -0700253 */
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();
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700265 doRecycle = mService.sendAccessibilityEvent(event, mUserId);
svetoslavganov75986cf2009-05-14 22:28:01 -0700266 Binder.restoreCallingIdentity(identityToken);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700267 if (DEBUG) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700268 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 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700280 * Requests feedback interruption from all accessibility services.
svetoslavganov75986cf2009-05-14 22:28:01 -0700281 */
282 public void interrupt() {
283 if (!mIsEnabled) {
284 throw new IllegalStateException("Accessibility off. Did you forget to check that?");
285 }
286 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700287 mService.interrupt(mUserId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700288 if (DEBUG) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700289 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.
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700300 *
301 * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
svetoslavganov75986cf2009-05-14 22:28:01 -0700302 */
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700303 @Deprecated
svetoslavganov75986cf2009-05-14 22:28:01 -0700304 public List<ServiceInfo> getAccessibilityServiceList() {
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700305 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;
svetoslavganov75986cf2009-05-14 22:28:01 -0700322 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700323 services = mService.getInstalledAccessibilityServiceList(mUserId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700324 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 /**
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700334 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700335 * for a given feedback type.
336 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700337 * @param feedbackTypeFlags The feedback type flags.
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700338 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700339 *
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
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700345 */
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700346 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
347 int feedbackTypeFlags) {
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700348 List<AccessibilityServiceInfo> services = null;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700349 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700350 services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700351 if (DEBUG) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700352 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 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700359
360 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700361 * Registers an {@link AccessibilityStateChangeListener} for changes in
362 * the global accessibility state of the system.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700363 *
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 /**
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700384 * 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 /**
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700395 * 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 {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700428 return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700429 } 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 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700448}