2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.view.accessibility;
19 import android.Manifest;
20 import android.accessibilityservice.AccessibilityServiceInfo;
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ServiceInfo;
25 import android.os.Binder;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.util.Log;
36 import android.view.IWindow;
37 import android.view.View;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.concurrent.CopyOnWriteArrayList;
45 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
46 * and provides facilities for querying the accessibility state of the system.
47 * Accessibility events are generated when something notable happens in the user interface,
48 * for example an {@link android.app.Activity} starts, the focus or selection of a
49 * {@link android.view.View} changes etc. Parties interested in handling accessibility
50 * events implement and register an accessibility service which extends
51 * {@link android.accessibilityservice.AccessibilityService}.
53 * To obtain a handle to the accessibility manager do the following:
57 * <pre>AccessibilityManager accessibilityManager =
58 * (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre>
62 * @see AccessibilityEvent
63 * @see AccessibilityNodeInfo
64 * @see android.accessibilityservice.AccessibilityService
65 * @see Context#getSystemService
66 * @see Context#ACCESSIBILITY_SERVICE
68 public final class AccessibilityManager {
69 private static final boolean DEBUG = false;
71 private static final String LOG_TAG = "AccessibilityManager";
74 public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
77 public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
80 public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
83 public static final int DALTONIZER_DISABLED = -1;
86 public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
89 public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
92 public static final int AUTOCLICK_DELAY_DEFAULT = 600;
94 static final Object sInstanceSync = new Object();
96 private static AccessibilityManager sInstance;
98 private final Object mLock = new Object();
100 private IAccessibilityManager mService;
104 final Handler mHandler;
108 boolean mIsTouchExplorationEnabled;
110 boolean mIsHighTextContrastEnabled;
112 private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
113 mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>();
115 private final CopyOnWriteArrayList<TouchExplorationStateChangeListener>
116 mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<>();
118 private final CopyOnWriteArrayList<HighTextContrastChangeListener>
119 mHighTextContrastStateChangeListeners = new CopyOnWriteArrayList<>();
122 * Listener for the system accessibility state. To listen for changes to the
123 * accessibility state on the device, implement this interface and register
124 * it with the system by calling {@link #addAccessibilityStateChangeListener}.
126 public interface AccessibilityStateChangeListener {
129 * Called when the accessibility enabled state changes.
131 * @param enabled Whether accessibility is enabled.
133 public void onAccessibilityStateChanged(boolean enabled);
137 * Listener for the system touch exploration state. To listen for changes to
138 * the touch exploration state on the device, implement this interface and
139 * register it with the system by calling
140 * {@link #addTouchExplorationStateChangeListener}.
142 public interface TouchExplorationStateChangeListener {
145 * Called when the touch exploration enabled state changes.
147 * @param enabled Whether touch exploration is enabled.
149 public void onTouchExplorationStateChanged(boolean enabled);
153 * Listener for the system high text contrast state. To listen for changes to
154 * the high text contrast state on the device, implement this interface and
155 * register it with the system by calling
156 * {@link #addHighTextContrastStateChangeListener}.
160 public interface HighTextContrastChangeListener {
163 * Called when the high text contrast enabled state changes.
165 * @param enabled Whether high text contrast is enabled.
167 public void onHighTextContrastStateChanged(boolean enabled);
170 private final IAccessibilityManagerClient.Stub mClient =
171 new IAccessibilityManagerClient.Stub() {
172 public void setState(int state) {
173 // We do not want to change this immediately as the applicatoin may
174 // have already checked that accessibility is on and fired an event,
175 // that is now propagating up the view tree, Hence, if accessibility
176 // is now off an exception will be thrown. We want to have the exception
177 // enforcement to guard against apps that fire unnecessary accessibility
178 // events when accessibility is off.
179 mHandler.obtainMessage(MyHandler.MSG_SET_STATE, state, 0).sendToTarget();
184 * Get an AccessibilityManager instance (create one if necessary).
186 * @param context Context in which this manager operates.
190 public static AccessibilityManager getInstance(Context context) {
191 synchronized (sInstanceSync) {
192 if (sInstance == null) {
194 if (Binder.getCallingUid() == Process.SYSTEM_UID
195 || context.checkCallingOrSelfPermission(
196 Manifest.permission.INTERACT_ACROSS_USERS)
197 == PackageManager.PERMISSION_GRANTED
198 || context.checkCallingOrSelfPermission(
199 Manifest.permission.INTERACT_ACROSS_USERS_FULL)
200 == PackageManager.PERMISSION_GRANTED) {
201 userId = UserHandle.USER_CURRENT;
203 userId = UserHandle.myUserId();
205 sInstance = new AccessibilityManager(context, null, userId);
212 * Create an instance.
214 * @param context A {@link Context}.
215 * @param service An interface to the backing service.
216 * @param userId User id under which to run.
220 public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
221 mHandler = new MyHandler(context.getMainLooper());
223 synchronized (mLock) {
224 tryConnectToServiceLocked(service);
231 public IAccessibilityManagerClient getClient() {
236 * Returns if the accessibility in the system is enabled.
238 * @return True if accessibility is enabled, false otherwise.
240 public boolean isEnabled() {
241 synchronized (mLock) {
242 IAccessibilityManager service = getServiceLocked();
243 if (service == null) {
251 * Returns if the touch exploration in the system is enabled.
253 * @return True if touch exploration is enabled, false otherwise.
255 public boolean isTouchExplorationEnabled() {
256 synchronized (mLock) {
257 IAccessibilityManager service = getServiceLocked();
258 if (service == null) {
261 return mIsTouchExplorationEnabled;
266 * Returns if the high text contrast in the system is enabled.
268 * <strong>Note:</strong> You need to query this only if you application is
269 * doing its own rendering and does not rely on the platform rendering pipeline.
272 * @return True if high text contrast is enabled, false otherwise.
276 public boolean isHighTextContrastEnabled() {
277 synchronized (mLock) {
278 IAccessibilityManager service = getServiceLocked();
279 if (service == null) {
282 return mIsHighTextContrastEnabled;
287 * Sends an {@link AccessibilityEvent}.
289 * @param event The event to send.
291 * @throws IllegalStateException if accessibility is not enabled.
293 * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
294 * events is through calling
295 * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
296 * instead of this method to allow predecessors to augment/filter events sent by
299 public void sendAccessibilityEvent(AccessibilityEvent event) {
300 final IAccessibilityManager service;
302 synchronized (mLock) {
303 service = getServiceLocked();
304 if (service == null) {
308 Looper myLooper = Looper.myLooper();
309 if (myLooper == Looper.getMainLooper()) {
310 throw new IllegalStateException(
311 "Accessibility off. Did you forget to check that?");
313 // If we're not running on the thread with the main looper, it's possible for
314 // the state of accessibility to change between checking isEnabled and
315 // calling this method. So just log the error rather than throwing the
317 Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
323 boolean doRecycle = false;
325 event.setEventTime(SystemClock.uptimeMillis());
326 // it is possible that this manager is in the same process as the service but
327 // client using it is called through Binder from another process. Example: MMS
328 // app adds a SMS notification and the NotificationManagerService calls this method
329 long identityToken = Binder.clearCallingIdentity();
330 doRecycle = service.sendAccessibilityEvent(event, userId);
331 Binder.restoreCallingIdentity(identityToken);
333 Log.i(LOG_TAG, event + " sent");
335 } catch (RemoteException re) {
336 Log.e(LOG_TAG, "Error during sending " + event + " ", re);
345 * Requests feedback interruption from all accessibility services.
347 public void interrupt() {
348 final IAccessibilityManager service;
350 synchronized (mLock) {
351 service = getServiceLocked();
352 if (service == null) {
356 throw new IllegalStateException("Accessibility off. Did you forget to check that?");
361 service.interrupt(userId);
363 Log.i(LOG_TAG, "Requested interrupt from all services");
365 } catch (RemoteException re) {
366 Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
371 * Returns the {@link ServiceInfo}s of the installed accessibility services.
373 * @return An unmodifiable list with {@link ServiceInfo}s.
375 * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
378 public List<ServiceInfo> getAccessibilityServiceList() {
379 List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
380 List<ServiceInfo> services = new ArrayList<>();
381 final int infoCount = infos.size();
382 for (int i = 0; i < infoCount; i++) {
383 AccessibilityServiceInfo info = infos.get(i);
384 services.add(info.getResolveInfo().serviceInfo);
386 return Collections.unmodifiableList(services);
390 * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
392 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
394 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
395 final IAccessibilityManager service;
397 synchronized (mLock) {
398 service = getServiceLocked();
399 if (service == null) {
400 return Collections.emptyList();
405 List<AccessibilityServiceInfo> services = null;
407 services = service.getInstalledAccessibilityServiceList(userId);
409 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
411 } catch (RemoteException re) {
412 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
414 if (services != null) {
415 return Collections.unmodifiableList(services);
417 return Collections.emptyList();
422 * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
423 * for a given feedback type.
425 * @param feedbackTypeFlags The feedback type flags.
426 * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
428 * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
429 * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
430 * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
431 * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
432 * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
433 * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
435 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
436 int feedbackTypeFlags) {
437 final IAccessibilityManager service;
439 synchronized (mLock) {
440 service = getServiceLocked();
441 if (service == null) {
442 return Collections.emptyList();
447 List<AccessibilityServiceInfo> services = null;
449 services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
451 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
453 } catch (RemoteException re) {
454 Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
456 if (services != null) {
457 return Collections.unmodifiableList(services);
459 return Collections.emptyList();
464 * Registers an {@link AccessibilityStateChangeListener} for changes in
465 * the global accessibility state of the system.
467 * @param listener The listener.
468 * @return True if successfully registered.
470 public boolean addAccessibilityStateChangeListener(
471 @NonNull AccessibilityStateChangeListener listener) {
472 // Final CopyOnWriteArrayList - no lock needed.
473 return mAccessibilityStateChangeListeners.add(listener);
477 * Unregisters an {@link AccessibilityStateChangeListener}.
479 * @param listener The listener.
480 * @return True if successfully unregistered.
482 public boolean removeAccessibilityStateChangeListener(
483 @NonNull AccessibilityStateChangeListener listener) {
484 // Final CopyOnWriteArrayList - no lock needed.
485 return mAccessibilityStateChangeListeners.remove(listener);
489 * Registers a {@link TouchExplorationStateChangeListener} for changes in
490 * the global touch exploration state of the system.
492 * @param listener The listener.
493 * @return True if successfully registered.
495 public boolean addTouchExplorationStateChangeListener(
496 @NonNull TouchExplorationStateChangeListener listener) {
497 // Final CopyOnWriteArrayList - no lock needed.
498 return mTouchExplorationStateChangeListeners.add(listener);
502 * Unregisters a {@link TouchExplorationStateChangeListener}.
504 * @param listener The listener.
505 * @return True if successfully unregistered.
507 public boolean removeTouchExplorationStateChangeListener(
508 @NonNull TouchExplorationStateChangeListener listener) {
509 // Final CopyOnWriteArrayList - no lock needed.
510 return mTouchExplorationStateChangeListeners.remove(listener);
514 * Registers a {@link HighTextContrastChangeListener} for changes in
515 * the global high text contrast state of the system.
517 * @param listener The listener.
518 * @return True if successfully registered.
522 public boolean addHighTextContrastStateChangeListener(
523 @NonNull HighTextContrastChangeListener listener) {
524 // Final CopyOnWriteArrayList - no lock needed.
525 return mHighTextContrastStateChangeListeners.add(listener);
529 * Unregisters a {@link HighTextContrastChangeListener}.
531 * @param listener The listener.
532 * @return True if successfully unregistered.
536 public boolean removeHighTextContrastStateChangeListener(
537 @NonNull HighTextContrastChangeListener listener) {
538 // Final CopyOnWriteArrayList - no lock needed.
539 return mHighTextContrastStateChangeListeners.remove(listener);
543 * Sets the current state and notifies listeners, if necessary.
545 * @param stateFlags The state flags.
547 private void setStateLocked(int stateFlags) {
548 final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
549 final boolean touchExplorationEnabled =
550 (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
551 final boolean highTextContrastEnabled =
552 (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
554 final boolean wasEnabled = mIsEnabled;
555 final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
556 final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
558 // Ensure listeners get current state from isZzzEnabled() calls.
559 mIsEnabled = enabled;
560 mIsTouchExplorationEnabled = touchExplorationEnabled;
561 mIsHighTextContrastEnabled = highTextContrastEnabled;
563 if (wasEnabled != enabled) {
564 mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
567 if (wasTouchExplorationEnabled != touchExplorationEnabled) {
568 mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
571 if (wasHighTextContrastEnabled != highTextContrastEnabled) {
572 mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
577 * Adds an accessibility interaction connection interface for a given window.
578 * @param windowToken The window token to which a connection is added.
579 * @param connection The connection.
583 public int addAccessibilityInteractionConnection(IWindow windowToken,
584 IAccessibilityInteractionConnection connection) {
585 final IAccessibilityManager service;
587 synchronized (mLock) {
588 service = getServiceLocked();
589 if (service == null) {
595 return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
596 } catch (RemoteException re) {
597 Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
603 * Removed an accessibility interaction connection interface for a given window.
604 * @param windowToken The window token to which a connection is removed.
608 public void removeAccessibilityInteractionConnection(IWindow windowToken) {
609 final IAccessibilityManager service;
610 synchronized (mLock) {
611 service = getServiceLocked();
612 if (service == null) {
617 service.removeAccessibilityInteractionConnection(windowToken);
618 } catch (RemoteException re) {
619 Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
623 private IAccessibilityManager getServiceLocked() {
624 if (mService == null) {
625 tryConnectToServiceLocked(null);
630 private void tryConnectToServiceLocked(IAccessibilityManager service) {
631 if (service == null) {
632 IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
633 if (iBinder == null) {
636 service = IAccessibilityManager.Stub.asInterface(iBinder);
640 final int stateFlags = service.addClient(mClient, mUserId);
641 setStateLocked(stateFlags);
643 } catch (RemoteException re) {
644 Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
649 * Notifies the registered {@link AccessibilityStateChangeListener}s.
651 private void handleNotifyAccessibilityStateChanged() {
652 final boolean isEnabled;
653 synchronized (mLock) {
654 isEnabled = mIsEnabled;
656 // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
657 for (AccessibilityStateChangeListener listener :mAccessibilityStateChangeListeners) {
658 listener.onAccessibilityStateChanged(isEnabled);
663 * Notifies the registered {@link TouchExplorationStateChangeListener}s.
665 private void handleNotifyTouchExplorationStateChanged() {
666 final boolean isTouchExplorationEnabled;
667 synchronized (mLock) {
668 isTouchExplorationEnabled = mIsTouchExplorationEnabled;
670 // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
671 for (TouchExplorationStateChangeListener listener :mTouchExplorationStateChangeListeners) {
672 listener.onTouchExplorationStateChanged(isTouchExplorationEnabled);
677 * Notifies the registered {@link HighTextContrastChangeListener}s.
679 private void handleNotifyHighTextContrastStateChanged() {
680 final boolean isHighTextContrastEnabled;
681 synchronized (mLock) {
682 isHighTextContrastEnabled = mIsHighTextContrastEnabled;
684 // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
685 for (HighTextContrastChangeListener listener : mHighTextContrastStateChangeListeners) {
686 listener.onHighTextContrastStateChanged(isHighTextContrastEnabled);
690 private final class MyHandler extends Handler {
691 public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
692 public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
693 public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3;
694 public static final int MSG_SET_STATE = 4;
696 public MyHandler(Looper looper) {
697 super(looper, null, false);
701 public void handleMessage(Message message) {
702 switch (message.what) {
703 case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
704 handleNotifyAccessibilityStateChanged();
707 case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: {
708 handleNotifyTouchExplorationStateChanged();
711 case MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED: {
712 handleNotifyHighTextContrastStateChanged();
715 case MSG_SET_STATE: {
716 // See comment at mClient
717 final int state = message.arg1;
718 synchronized (mLock) {
719 setStateLocked(state);