AccessibilityManager.java revision 576b6f2c25ab388e9dbc3bfc4df445ca648e7253
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.Manifest;
20import android.accessibilityservice.AccessibilityServiceInfo;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.content.pm.ServiceInfo;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
29import android.os.Process;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.SystemClock;
33import android.os.UserHandle;
34import android.util.Log;
35import android.view.IWindow;
36import android.view.View;
37
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.List;
41import java.util.concurrent.CopyOnWriteArrayList;
42
43/**
44 * 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,
47 * 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}.
51 * <p>
52 * To obtain a handle to the accessibility manager do the following:
53 * </p>
54 * <p>
55 * <code>
56 * <pre>AccessibilityManager accessibilityManager =
57 *        (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre>
58 * </code>
59 * </p>
60 *
61 * @see AccessibilityEvent
62 * @see AccessibilityNodeInfo
63 * @see android.accessibilityservice.AccessibilityService
64 * @see Context#getSystemService
65 * @see Context#ACCESSIBILITY_SERVICE
66 */
67public final class AccessibilityManager {
68    private static final boolean DEBUG = false;
69
70    private static final String LOG_TAG = "AccessibilityManager";
71
72    /** @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
78    /** @hide */
79    public static final int INVERSION_DISABLED = -1;
80
81    /** @hide */
82    public static final int INVERSION_STANDARD = 0;
83
84    /** @hide */
85    public static final int INVERSION_HUE_ONLY = 1;
86
87    /** @hide */
88    public static final int INVERSION_VALUE_ONLY = 2;
89
90    /** @hide */
91    public static final int DALTONIZER_DISABLED = -1;
92
93    /** @hide */
94    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
95
96    /** @hide */
97    public static final int DALTONIZER_SIMULATE_PROTANOMALY = 1;
98
99    /** @hide */
100    public static final int DALTONIZER_SIMULATE_DEUTERANOMALY = 2;
101
102    /** @hide */
103    public static final int DALTONIZER_SIMULATE_TRITANOMALY = 3;
104
105    /** @hide */
106    public static final int DALTONIZER_CORRECT_PROTANOMALY = 11;
107
108    /** @hide */
109    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
110
111    /** @hide */
112    public static final int DALTONIZER_CORRECT_TRITANOMALY = 13;
113
114    static final Object sInstanceSync = new Object();
115
116    private static AccessibilityManager sInstance;
117
118    private final Object mLock = new Object();
119
120    private IAccessibilityManager mService;
121
122    final int mUserId;
123
124    final Handler mHandler;
125
126    boolean mIsEnabled;
127
128    boolean mIsTouchExplorationEnabled;
129
130    private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
131            mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<
132                    AccessibilityStateChangeListener>();
133
134    private final CopyOnWriteArrayList<TouchExplorationStateChangeListener>
135            mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<
136                    TouchExplorationStateChangeListener>();
137
138    /**
139     * Listener for the system accessibility state. To listen for changes to the
140     * accessibility state on the device, implement this interface and register
141     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
142     */
143    public interface AccessibilityStateChangeListener {
144
145        /**
146         * Called when the accessibility enabled state changes.
147         *
148         * @param enabled Whether accessibility is enabled.
149         */
150        public void onAccessibilityStateChanged(boolean enabled);
151    }
152
153    /**
154     * Listener for the system touch exploration state. To listen for changes to
155     * the touch exploration state on the device, implement this interface and
156     * register it with the system by calling
157     * {@link #addTouchExplorationStateChangeListener}.
158     */
159    public interface TouchExplorationStateChangeListener {
160
161        /**
162         * Called when the touch exploration enabled state changes.
163         *
164         * @param enabled Whether touch exploration is enabled.
165         */
166        public void onTouchExplorationStateChanged(boolean enabled);
167    }
168
169    private final IAccessibilityManagerClient.Stub mClient =
170            new IAccessibilityManagerClient.Stub() {
171        public void setState(int state) {
172            synchronized (mLock) {
173                setStateLocked(state);
174            }
175        }
176    };
177
178    /**
179     * Get an AccessibilityManager instance (create one if necessary).
180     *
181     * @param context Context in which this manager operates.
182     *
183     * @hide
184     */
185    public static AccessibilityManager getInstance(Context context) {
186        synchronized (sInstanceSync) {
187            if (sInstance == null) {
188                final int userId;
189                if (Binder.getCallingUid() == Process.SYSTEM_UID
190                        || context.checkCallingOrSelfPermission(
191                                Manifest.permission.INTERACT_ACROSS_USERS)
192                                        == PackageManager.PERMISSION_GRANTED
193                        || context.checkCallingOrSelfPermission(
194                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
195                                        == PackageManager.PERMISSION_GRANTED) {
196                    userId = UserHandle.USER_CURRENT;
197                } else {
198                    userId = UserHandle.myUserId();
199                }
200                IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
201                IAccessibilityManager service = iBinder == null
202                        ? null : IAccessibilityManager.Stub.asInterface(iBinder);
203                sInstance = new AccessibilityManager(context, service, userId);
204            }
205        }
206        return sInstance;
207    }
208
209    /**
210     * Create an instance.
211     *
212     * @param context A {@link Context}.
213     * @param service An interface to the backing service.
214     * @param userId User id under which to run.
215     *
216     * @hide
217     */
218    public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
219        mHandler = new MyHandler(context.getMainLooper());
220        mService = service;
221        mUserId = userId;
222        synchronized (mLock) {
223            tryConnectToServiceLocked();
224        }
225    }
226
227    /**
228     * @hide
229     */
230    public IAccessibilityManagerClient getClient() {
231        return mClient;
232    }
233
234    /**
235     * Returns if the accessibility in the system is enabled.
236     *
237     * @return True if accessibility is enabled, false otherwise.
238     */
239    public boolean isEnabled() {
240        synchronized (mLock) {
241            IAccessibilityManager service = getServiceLocked();
242            if (service == null) {
243                return false;
244            }
245            return mIsEnabled;
246        }
247    }
248
249    /**
250     * Returns if the touch exploration in the system is enabled.
251     *
252     * @return True if touch exploration is enabled, false otherwise.
253     */
254    public boolean isTouchExplorationEnabled() {
255        synchronized (mLock) {
256            IAccessibilityManager service = getServiceLocked();
257            if (service == null) {
258                return false;
259            }
260            return mIsTouchExplorationEnabled;
261        }
262    }
263
264    /**
265     * Sends an {@link AccessibilityEvent}.
266     *
267     * @param event The event to send.
268     *
269     * @throws IllegalStateException if accessibility is not enabled.
270     *
271     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
272     * events is through calling
273     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
274     * instead of this method to allow predecessors to augment/filter events sent by
275     * their descendants.
276     */
277    public void sendAccessibilityEvent(AccessibilityEvent event) {
278        final IAccessibilityManager service;
279        final int userId;
280        synchronized (mLock) {
281            service = getServiceLocked();
282            if (service == null) {
283                return;
284            }
285            if (!mIsEnabled) {
286                throw new IllegalStateException("Accessibility off. Did you forget to check that?");
287            }
288            userId = mUserId;
289        }
290        boolean doRecycle = false;
291        try {
292            event.setEventTime(SystemClock.uptimeMillis());
293            // it is possible that this manager is in the same process as the service but
294            // client using it is called through Binder from another process. Example: MMS
295            // app adds a SMS notification and the NotificationManagerService calls this method
296            long identityToken = Binder.clearCallingIdentity();
297            doRecycle = service.sendAccessibilityEvent(event, userId);
298            Binder.restoreCallingIdentity(identityToken);
299            if (DEBUG) {
300                Log.i(LOG_TAG, event + " sent");
301            }
302        } catch (RemoteException re) {
303            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
304        } finally {
305            if (doRecycle) {
306                event.recycle();
307            }
308        }
309    }
310
311    /**
312     * Requests feedback interruption from all accessibility services.
313     */
314    public void interrupt() {
315        final IAccessibilityManager service;
316        final int userId;
317        synchronized (mLock) {
318            service = getServiceLocked();
319            if (service == null) {
320                return;
321            }
322            if (!mIsEnabled) {
323                throw new IllegalStateException("Accessibility off. Did you forget to check that?");
324            }
325            userId = mUserId;
326        }
327        try {
328            service.interrupt(userId);
329            if (DEBUG) {
330                Log.i(LOG_TAG, "Requested interrupt from all services");
331            }
332        } catch (RemoteException re) {
333            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
334        }
335    }
336
337    /**
338     * Returns the {@link ServiceInfo}s of the installed accessibility services.
339     *
340     * @return An unmodifiable list with {@link ServiceInfo}s.
341     *
342     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
343     */
344    @Deprecated
345    public List<ServiceInfo> getAccessibilityServiceList() {
346        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
347        List<ServiceInfo> services = new ArrayList<ServiceInfo>();
348        final int infoCount = infos.size();
349        for (int i = 0; i < infoCount; i++) {
350            AccessibilityServiceInfo info = infos.get(i);
351            services.add(info.getResolveInfo().serviceInfo);
352        }
353        return Collections.unmodifiableList(services);
354    }
355
356    /**
357     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
358     *
359     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
360     */
361    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
362        final IAccessibilityManager service;
363        final int userId;
364        synchronized (mLock) {
365            service = getServiceLocked();
366            if (service == null) {
367                return Collections.emptyList();
368            }
369            userId = mUserId;
370        }
371
372        List<AccessibilityServiceInfo> services = null;
373        try {
374            services = service.getInstalledAccessibilityServiceList(userId);
375            if (DEBUG) {
376                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
377            }
378        } catch (RemoteException re) {
379            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
380        }
381        if (services != null) {
382            return Collections.unmodifiableList(services);
383        } else {
384            return Collections.emptyList();
385        }
386    }
387
388    /**
389     * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
390     * for a given feedback type.
391     *
392     * @param feedbackTypeFlags The feedback type flags.
393     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
394     *
395     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
396     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
397     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
398     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
399     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
400     */
401    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
402            int feedbackTypeFlags) {
403        final IAccessibilityManager service;
404        final int userId;
405        synchronized (mLock) {
406            service = getServiceLocked();
407            if (service == null) {
408                return Collections.emptyList();
409            }
410            userId = mUserId;
411        }
412
413        List<AccessibilityServiceInfo> services = null;
414        try {
415            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
416            if (DEBUG) {
417                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
418            }
419        } catch (RemoteException re) {
420            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
421        }
422        if (services != null) {
423            return Collections.unmodifiableList(services);
424        } else {
425            return Collections.emptyList();
426        }
427    }
428
429    /**
430     * Registers an {@link AccessibilityStateChangeListener} for changes in
431     * the global accessibility state of the system.
432     *
433     * @param listener The listener.
434     * @return True if successfully registered.
435     */
436    public boolean addAccessibilityStateChangeListener(
437            AccessibilityStateChangeListener listener) {
438        // Final CopyOnArrayList - no lock needed.
439        return mAccessibilityStateChangeListeners.add(listener);
440    }
441
442    /**
443     * Unregisters an {@link AccessibilityStateChangeListener}.
444     *
445     * @param listener The listener.
446     * @return True if successfully unregistered.
447     */
448    public boolean removeAccessibilityStateChangeListener(
449            AccessibilityStateChangeListener listener) {
450        // Final CopyOnArrayList - no lock needed.
451        return mAccessibilityStateChangeListeners.remove(listener);
452    }
453
454    /**
455     * Registers a {@link TouchExplorationStateChangeListener} for changes in
456     * the global touch exploration state of the system.
457     *
458     * @param listener The listener.
459     * @return True if successfully registered.
460     */
461    public boolean addTouchExplorationStateChangeListener(
462            TouchExplorationStateChangeListener listener) {
463        // Final CopyOnArrayList - no lock needed.
464        return mTouchExplorationStateChangeListeners.add(listener);
465    }
466
467    /**
468     * Unregisters a {@link TouchExplorationStateChangeListener}.
469     *
470     * @param listener The listener.
471     * @return True if successfully unregistered.
472     */
473    public boolean removeTouchExplorationStateChangeListener(
474            TouchExplorationStateChangeListener listener) {
475        // Final CopyOnArrayList - no lock needed.
476        return mTouchExplorationStateChangeListeners.remove(listener);
477    }
478
479    /**
480     * Sets the current state and notifies listeners, if necessary.
481     *
482     * @param stateFlags The state flags.
483     */
484    private void setStateLocked(int stateFlags) {
485        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
486        final boolean touchExplorationEnabled =
487                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
488
489        final boolean wasEnabled = mIsEnabled;
490        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
491
492        // Ensure listeners get current state from isZzzEnabled() calls.
493        mIsEnabled = enabled;
494        mIsTouchExplorationEnabled = touchExplorationEnabled;
495
496        if (wasEnabled != enabled) {
497            mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
498        }
499
500        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
501            mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
502        }
503    }
504
505    /**
506     * Adds an accessibility interaction connection interface for a given window.
507     * @param windowToken The window token to which a connection is added.
508     * @param connection The connection.
509     *
510     * @hide
511     */
512    public int addAccessibilityInteractionConnection(IWindow windowToken,
513            IAccessibilityInteractionConnection connection) {
514        final IAccessibilityManager service;
515        final int userId;
516        synchronized (mLock) {
517            service = getServiceLocked();
518            if (service == null) {
519                return View.NO_ID;
520            }
521            userId = mUserId;
522        }
523        try {
524            return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
525        } catch (RemoteException re) {
526            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
527        }
528        return View.NO_ID;
529    }
530
531    /**
532     * Removed an accessibility interaction connection interface for a given window.
533     * @param windowToken The window token to which a connection is removed.
534     *
535     * @hide
536     */
537    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
538        final IAccessibilityManager service;
539        synchronized (mLock) {
540            service = getServiceLocked();
541            if (service == null) {
542                return;
543            }
544        }
545        try {
546            service.removeAccessibilityInteractionConnection(windowToken);
547        } catch (RemoteException re) {
548            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
549        }
550    }
551
552    private  IAccessibilityManager getServiceLocked() {
553        if (mService == null) {
554            tryConnectToServiceLocked();
555        }
556        return mService;
557    }
558
559    private void tryConnectToServiceLocked() {
560        IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
561        if (iBinder == null) {
562            return;
563        }
564        IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
565        try {
566            final int stateFlags = service.addClient(mClient, mUserId);
567            setStateLocked(stateFlags);
568            mService = service;
569        } catch (RemoteException re) {
570            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
571        }
572    }
573
574    /**
575     * Notifies the registered {@link AccessibilityStateChangeListener}s.
576     */
577    private void handleNotifyAccessibilityStateChanged() {
578        final boolean isEnabled;
579        synchronized (mLock) {
580            isEnabled = mIsEnabled;
581        }
582        final int listenerCount = mAccessibilityStateChangeListeners.size();
583        for (int i = 0; i < listenerCount; i++) {
584            mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(isEnabled);
585        }
586    }
587
588    /**
589     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
590     */
591    private void handleNotifyTouchExplorationStateChanged() {
592        final boolean isTouchExplorationEnabled;
593        synchronized (mLock) {
594            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
595        }
596        final int listenerCount = mTouchExplorationStateChangeListeners.size();
597        for (int i = 0; i < listenerCount; i++) {
598            mTouchExplorationStateChangeListeners.get(i)
599                    .onTouchExplorationStateChanged(isTouchExplorationEnabled);
600        }
601    }
602
603    private final class MyHandler extends Handler {
604        public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
605        public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
606
607        public MyHandler(Looper looper) {
608            super(looper, null, false);
609        }
610
611        @Override
612        public void handleMessage(Message message) {
613            switch (message.what) {
614                case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
615                    handleNotifyAccessibilityStateChanged();
616                } break;
617
618                case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: {
619                    handleNotifyTouchExplorationStateChanged();
620                }
621            }
622        }
623    }
624}
625