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