blob: 04ce7e2665b88b072226ab6eb0b8bf7a2f97a9cb [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
Svetoslav6b25e722013-02-28 16:55:54 -080019import android.Manifest;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070020import android.accessibilityservice.AccessibilityServiceInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -070021import android.content.Context;
Svetoslav6b25e722013-02-28 16:55:54 -080022import android.content.pm.PackageManager;
svetoslavganov75986cf2009-05-14 22:28:01 -070023import android.content.pm.ServiceInfo;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
Svetoslav6b25e722013-02-28 16:55:54 -080029import android.os.Process;
svetoslavganov75986cf2009-05-14 22:28:01 -070030import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.SystemClock;
Svetoslav Ganov58d37b52012-09-18 12:04:19 -070033import android.os.UserHandle;
svetoslavganov75986cf2009-05-14 22:28:01 -070034import android.util.Log;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070035import android.view.IWindow;
36import android.view.View;
svetoslavganov75986cf2009-05-14 22:28:01 -070037
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -070038import java.util.ArrayList;
svetoslavganov75986cf2009-05-14 22:28:01 -070039import java.util.Collections;
40import java.util.List;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070041import java.util.concurrent.CopyOnWriteArrayList;
svetoslavganov75986cf2009-05-14 22:28:01 -070042
43/**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070044 * 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,
svetoslavganov75986cf2009-05-14 22:28:01 -070047 * 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}.
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070051 * <p>
52 * To obtain a handle to the accessibility manager do the following:
53 * </p>
54 * <p>
55 * <code>
Scott Mainb303d832011-10-12 16:45:18 -070056 * <pre>AccessibilityManager accessibilityManager =
57 * (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070058 * </code>
59 * </p>
svetoslavganov75986cf2009-05-14 22:28:01 -070060 *
61 * @see AccessibilityEvent
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070062 * @see AccessibilityNodeInfo
svetoslavganov75986cf2009-05-14 22:28:01 -070063 * @see android.accessibilityservice.AccessibilityService
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070064 * @see Context#getSystemService
65 * @see Context#ACCESSIBILITY_SERVICE
svetoslavganov75986cf2009-05-14 22:28:01 -070066 */
67public final class AccessibilityManager {
Svetoslav Ganov736c2752011-04-22 18:30:36 -070068 private static final boolean DEBUG = false;
69
svetoslavganov75986cf2009-05-14 22:28:01 -070070 private static final String LOG_TAG = "AccessibilityManager";
71
Svetoslav Ganov00aabf72011-07-21 11:35:03 -070072 /** @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
svetoslavganov75986cf2009-05-14 22:28:01 -070078 static final Object sInstanceSync = new Object();
79
80 private static AccessibilityManager sInstance;
81
Svetoslav Ganov00aabf72011-07-21 11:35:03 -070082 private static final int DO_SET_STATE = 10;
svetoslavganov75986cf2009-05-14 22:28:01 -070083
84 final IAccessibilityManager mService;
85
Svetoslav Ganov58d37b52012-09-18 12:04:19 -070086 final int mUserId;
87
svetoslavganov75986cf2009-05-14 22:28:01 -070088 final Handler mHandler;
89
90 boolean mIsEnabled;
91
Svetoslav Ganov35bfede2011-07-14 17:57:06 -070092 boolean mIsTouchExplorationEnabled;
93
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070094 final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners =
95 new CopyOnWriteArrayList<AccessibilityStateChangeListener>();
96
97 /**
Scott Mainb303d832011-10-12 16:45:18 -070098 * Listener for the system accessibility state. To listen for changes to the accessibility
99 * state on the device, implement this interface and register it with the system by
100 * calling {@link AccessibilityManager#addAccessibilityStateChangeListener
101 * addAccessibilityStateChangeListener()}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700102 */
103 public interface AccessibilityStateChangeListener {
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700104
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700105 /**
106 * Called back on change in the accessibility state.
107 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700108 * @param enabled Whether accessibility is enabled.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700109 */
110 public void onAccessibilityStateChanged(boolean enabled);
111 }
112
svetoslavganov75986cf2009-05-14 22:28:01 -0700113 final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700114 public void setState(int state) {
115 mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget();
svetoslavganov75986cf2009-05-14 22:28:01 -0700116 }
117 };
118
119 class MyHandler extends Handler {
120
121 MyHandler(Looper mainLooper) {
122 super(mainLooper);
123 }
124
125 @Override
126 public void handleMessage(Message message) {
127 switch (message.what) {
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700128 case DO_SET_STATE :
129 setState(message.arg1);
svetoslavganov75986cf2009-05-14 22:28:01 -0700130 return;
131 default :
132 Log.w(LOG_TAG, "Unknown message type: " + message.what);
133 }
134 }
135 }
136
137 /**
138 * Get an AccessibilityManager instance (create one if necessary).
139 *
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700140 * @param context Context in which this manager operates.
141 *
svetoslavganov75986cf2009-05-14 22:28:01 -0700142 * @hide
143 */
144 public static AccessibilityManager getInstance(Context context) {
145 synchronized (sInstanceSync) {
146 if (sInstance == null) {
Svetoslav6b25e722013-02-28 16:55:54 -0800147 final int userId;
148 if (Binder.getCallingUid() == Process.SYSTEM_UID
149 || context.checkCallingOrSelfPermission(
150 Manifest.permission.INTERACT_ACROSS_USERS)
151 == PackageManager.PERMISSION_GRANTED
152 || context.checkCallingOrSelfPermission(
153 Manifest.permission.INTERACT_ACROSS_USERS_FULL)
154 == PackageManager.PERMISSION_GRANTED) {
155 userId = UserHandle.USER_CURRENT;
156 } else {
157 userId = UserHandle.myUserId();
158 }
159 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
160 IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
161 sInstance = new AccessibilityManager(context, service, userId);
svetoslavganov75986cf2009-05-14 22:28:01 -0700162 }
163 }
164 return sInstance;
165 }
166
167 /**
168 * Create an instance.
169 *
170 * @param context A {@link Context}.
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700171 * @param service An interface to the backing service.
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700172 * @param userId User id under which to run.
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700173 *
174 * @hide
svetoslavganov75986cf2009-05-14 22:28:01 -0700175 */
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700176 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700177 mHandler = new MyHandler(context.getMainLooper());
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700178 mService = service;
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700179 mUserId = userId;
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700180
svetoslavganov75986cf2009-05-14 22:28:01 -0700181 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700182 final int stateFlags = mService.addClient(mClient, userId);
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700183 setState(stateFlags);
svetoslavganov75986cf2009-05-14 22:28:01 -0700184 } catch (RemoteException re) {
185 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
186 }
187 }
188
189 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700190 * Returns if the accessibility in the system is enabled.
svetoslavganov75986cf2009-05-14 22:28:01 -0700191 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700192 * @return True if accessibility is enabled, false otherwise.
svetoslavganov75986cf2009-05-14 22:28:01 -0700193 */
194 public boolean isEnabled() {
195 synchronized (mHandler) {
196 return mIsEnabled;
197 }
198 }
199
200 /**
Svetoslav Ganov35bfede2011-07-14 17:57:06 -0700201 * Returns if the touch exploration in the system is enabled.
202 *
203 * @return True if touch exploration is enabled, false otherwise.
204 */
205 public boolean isTouchExplorationEnabled() {
206 synchronized (mHandler) {
207 return mIsTouchExplorationEnabled;
208 }
209 }
210
211 /**
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700212 * Returns the client interface this instance registers in
213 * the centralized accessibility manager service.
214 *
215 * @return The client.
216 *
217 * @hide
218 */
219 public IAccessibilityManagerClient getClient() {
Jim Miller4dfecf52011-06-30 15:45:35 -0700220 return (IAccessibilityManagerClient) mClient.asBinder();
Svetoslav Ganovaf7adab2010-04-16 18:07:48 -0700221 }
222
223 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700224 * Sends an {@link AccessibilityEvent}.
svetoslavganov75986cf2009-05-14 22:28:01 -0700225 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700226 * @param event The event to send.
svetoslavganov75986cf2009-05-14 22:28:01 -0700227 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700228 * @throws IllegalStateException if accessibility is not enabled.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700229 *
230 * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
231 * events is through calling
232 * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
233 * instead of this method to allow predecessors to augment/filter events sent by
234 * their descendants.
svetoslavganov75986cf2009-05-14 22:28:01 -0700235 */
236 public void sendAccessibilityEvent(AccessibilityEvent event) {
237 if (!mIsEnabled) {
238 throw new IllegalStateException("Accessibility off. Did you forget to check that?");
239 }
240 boolean doRecycle = false;
241 try {
242 event.setEventTime(SystemClock.uptimeMillis());
243 // it is possible that this manager is in the same process as the service but
244 // client using it is called through Binder from another process. Example: MMS
245 // app adds a SMS notification and the NotificationManagerService calls this method
246 long identityToken = Binder.clearCallingIdentity();
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700247 doRecycle = mService.sendAccessibilityEvent(event, mUserId);
svetoslavganov75986cf2009-05-14 22:28:01 -0700248 Binder.restoreCallingIdentity(identityToken);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700249 if (DEBUG) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700250 Log.i(LOG_TAG, event + " sent");
251 }
252 } catch (RemoteException re) {
253 Log.e(LOG_TAG, "Error during sending " + event + " ", re);
254 } finally {
255 if (doRecycle) {
256 event.recycle();
257 }
258 }
259 }
260
261 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700262 * Requests feedback interruption from all accessibility services.
svetoslavganov75986cf2009-05-14 22:28:01 -0700263 */
264 public void interrupt() {
265 if (!mIsEnabled) {
266 throw new IllegalStateException("Accessibility off. Did you forget to check that?");
267 }
268 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700269 mService.interrupt(mUserId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700270 if (DEBUG) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700271 Log.i(LOG_TAG, "Requested interrupt from all services");
272 }
273 } catch (RemoteException re) {
274 Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
275 }
276 }
277
278 /**
279 * Returns the {@link ServiceInfo}s of the installed accessibility services.
280 *
281 * @return An unmodifiable list with {@link ServiceInfo}s.
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700282 *
283 * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
svetoslavganov75986cf2009-05-14 22:28:01 -0700284 */
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700285 @Deprecated
svetoslavganov75986cf2009-05-14 22:28:01 -0700286 public List<ServiceInfo> getAccessibilityServiceList() {
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700287 List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
288 List<ServiceInfo> services = new ArrayList<ServiceInfo>();
289 final int infoCount = infos.size();
290 for (int i = 0; i < infoCount; i++) {
291 AccessibilityServiceInfo info = infos.get(i);
292 services.add(info.getResolveInfo().serviceInfo);
293 }
294 return Collections.unmodifiableList(services);
295 }
296
297 /**
298 * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
299 *
300 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
301 */
302 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
303 List<AccessibilityServiceInfo> services = null;
svetoslavganov75986cf2009-05-14 22:28:01 -0700304 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700305 services = mService.getInstalledAccessibilityServiceList(mUserId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700306 if (DEBUG) {
307 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
308 }
309 } catch (RemoteException re) {
310 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
311 }
312 return Collections.unmodifiableList(services);
313 }
314
315 /**
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700316 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700317 * for a given feedback type.
318 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700319 * @param feedbackTypeFlags The feedback type flags.
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700320 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700321 *
322 * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
323 * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
324 * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
325 * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
326 * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700327 */
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700328 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
329 int feedbackTypeFlags) {
Svetoslav Ganovcc4053e2011-05-23 13:37:44 -0700330 List<AccessibilityServiceInfo> services = null;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700331 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700332 services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700333 if (DEBUG) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700334 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
335 }
336 } catch (RemoteException re) {
337 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
338 }
339 return Collections.unmodifiableList(services);
340 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700341
342 /**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700343 * Registers an {@link AccessibilityStateChangeListener} for changes in
344 * the global accessibility state of the system.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700345 *
346 * @param listener The listener.
347 * @return True if successfully registered.
348 */
349 public boolean addAccessibilityStateChangeListener(
350 AccessibilityStateChangeListener listener) {
351 return mAccessibilityStateChangeListeners.add(listener);
352 }
353
354 /**
355 * Unregisters an {@link AccessibilityStateChangeListener}.
356 *
357 * @param listener The listener.
358 * @return True if successfully unregistered.
359 */
360 public boolean removeAccessibilityStateChangeListener(
361 AccessibilityStateChangeListener listener) {
362 return mAccessibilityStateChangeListeners.remove(listener);
363 }
364
365 /**
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700366 * Sets the current state.
367 *
368 * @param stateFlags The state flags.
369 */
370 private void setState(int stateFlags) {
371 final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
372 setAccessibilityState(accessibilityEnabled);
373 mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
374 }
375
376 /**
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700377 * Sets the enabled state.
378 *
379 * @param isEnabled The accessibility state.
380 */
381 private void setAccessibilityState(boolean isEnabled) {
382 synchronized (mHandler) {
383 if (isEnabled != mIsEnabled) {
384 mIsEnabled = isEnabled;
385 notifyAccessibilityStateChanged();
386 }
387 }
388 }
389
390 /**
391 * Notifies the registered {@link AccessibilityStateChangeListener}s.
392 */
393 private void notifyAccessibilityStateChanged() {
394 final int listenerCount = mAccessibilityStateChangeListeners.size();
395 for (int i = 0; i < listenerCount; i++) {
396 mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
397 }
398 }
399
400 /**
401 * Adds an accessibility interaction connection interface for a given window.
402 * @param windowToken The window token to which a connection is added.
403 * @param connection The connection.
404 *
405 * @hide
406 */
407 public int addAccessibilityInteractionConnection(IWindow windowToken,
408 IAccessibilityInteractionConnection connection) {
409 try {
Svetoslav Ganov58d37b52012-09-18 12:04:19 -0700410 return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700411 } catch (RemoteException re) {
412 Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
413 }
414 return View.NO_ID;
415 }
416
417 /**
418 * Removed an accessibility interaction connection interface for a given window.
419 * @param windowToken The window token to which a connection is removed.
420 *
421 * @hide
422 */
423 public void removeAccessibilityInteractionConnection(IWindow windowToken) {
424 try {
425 mService.removeAccessibilityInteractionConnection(windowToken);
426 } catch (RemoteException re) {
427 Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
428 }
429 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700430}