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    /** @hide */
92    public static final int AUTOCLICK_DELAY_DEFAULT = 600;
93
94    static final Object sInstanceSync = new Object();
95
96    private static AccessibilityManager sInstance;
97
98    private final Object mLock = new Object();
99
100    private IAccessibilityManager mService;
101
102    final int mUserId;
103
104    final Handler mHandler;
105
106    boolean mIsEnabled;
107
108    boolean mIsTouchExplorationEnabled;
109
110    boolean mIsHighTextContrastEnabled;
111
112    private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
113            mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>();
114
115    private final CopyOnWriteArrayList<TouchExplorationStateChangeListener>
116            mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<>();
117
118    private final CopyOnWriteArrayList<HighTextContrastChangeListener>
119            mHighTextContrastStateChangeListeners = new CopyOnWriteArrayList<>();
120
121    /**
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}.
125     */
126    public interface AccessibilityStateChangeListener {
127
128        /**
129         * Called when the accessibility enabled state changes.
130         *
131         * @param enabled Whether accessibility is enabled.
132         */
133        public void onAccessibilityStateChanged(boolean enabled);
134    }
135
136    /**
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}.
141     */
142    public interface TouchExplorationStateChangeListener {
143
144        /**
145         * Called when the touch exploration enabled state changes.
146         *
147         * @param enabled Whether touch exploration is enabled.
148         */
149        public void onTouchExplorationStateChanged(boolean enabled);
150    }
151
152    /**
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}.
157     *
158     * @hide
159     */
160    public interface HighTextContrastChangeListener {
161
162        /**
163         * Called when the high text contrast enabled state changes.
164         *
165         * @param enabled Whether high text contrast is enabled.
166         */
167        public void onHighTextContrastStateChanged(boolean enabled);
168    }
169
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();
180        }
181    };
182
183    /**
184     * Get an AccessibilityManager instance (create one if necessary).
185     *
186     * @param context Context in which this manager operates.
187     *
188     * @hide
189     */
190    public static AccessibilityManager getInstance(Context context) {
191        synchronized (sInstanceSync) {
192            if (sInstance == null) {
193                final int userId;
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;
202                } else {
203                    userId = UserHandle.myUserId();
204                }
205                sInstance = new AccessibilityManager(context, null, 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        mUserId = userId;
223        synchronized (mLock) {
224            tryConnectToServiceLocked(service);
225        }
226    }
227
228    /**
229     * @hide
230     */
231    public IAccessibilityManagerClient getClient() {
232        return mClient;
233    }
234
235    /**
236     * Returns if the accessibility in the system is enabled.
237     *
238     * @return True if accessibility is enabled, false otherwise.
239     */
240    public boolean isEnabled() {
241        synchronized (mLock) {
242            IAccessibilityManager service = getServiceLocked();
243            if (service == null) {
244                return false;
245            }
246            return mIsEnabled;
247        }
248    }
249
250    /**
251     * Returns if the touch exploration in the system is enabled.
252     *
253     * @return True if touch exploration is enabled, false otherwise.
254     */
255    public boolean isTouchExplorationEnabled() {
256        synchronized (mLock) {
257            IAccessibilityManager service = getServiceLocked();
258            if (service == null) {
259                return false;
260            }
261            return mIsTouchExplorationEnabled;
262        }
263    }
264
265    /**
266     * Returns if the high text contrast in the system is enabled.
267     * <p>
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.
270     * </p>
271     *
272     * @return True if high text contrast is enabled, false otherwise.
273     *
274     * @hide
275     */
276    public boolean isHighTextContrastEnabled() {
277        synchronized (mLock) {
278            IAccessibilityManager service = getServiceLocked();
279            if (service == null) {
280                return false;
281            }
282            return mIsHighTextContrastEnabled;
283        }
284    }
285
286    /**
287     * Sends an {@link AccessibilityEvent}.
288     *
289     * @param event The event to send.
290     *
291     * @throws IllegalStateException if accessibility is not enabled.
292     *
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
297     * their descendants.
298     */
299    public void sendAccessibilityEvent(AccessibilityEvent event) {
300        final IAccessibilityManager service;
301        final int userId;
302        synchronized (mLock) {
303            service = getServiceLocked();
304            if (service == null) {
305                return;
306            }
307            if (!mIsEnabled) {
308                throw new IllegalStateException("Accessibility off. Did you forget to check that?");
309            }
310            userId = mUserId;
311        }
312        boolean doRecycle = false;
313        try {
314            event.setEventTime(SystemClock.uptimeMillis());
315            // it is possible that this manager is in the same process as the service but
316            // client using it is called through Binder from another process. Example: MMS
317            // app adds a SMS notification and the NotificationManagerService calls this method
318            long identityToken = Binder.clearCallingIdentity();
319            doRecycle = service.sendAccessibilityEvent(event, userId);
320            Binder.restoreCallingIdentity(identityToken);
321            if (DEBUG) {
322                Log.i(LOG_TAG, event + " sent");
323            }
324        } catch (RemoteException re) {
325            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
326        } finally {
327            if (doRecycle) {
328                event.recycle();
329            }
330        }
331    }
332
333    /**
334     * Requests feedback interruption from all accessibility services.
335     */
336    public void interrupt() {
337        final IAccessibilityManager service;
338        final int userId;
339        synchronized (mLock) {
340            service = getServiceLocked();
341            if (service == null) {
342                return;
343            }
344            if (!mIsEnabled) {
345                throw new IllegalStateException("Accessibility off. Did you forget to check that?");
346            }
347            userId = mUserId;
348        }
349        try {
350            service.interrupt(userId);
351            if (DEBUG) {
352                Log.i(LOG_TAG, "Requested interrupt from all services");
353            }
354        } catch (RemoteException re) {
355            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
356        }
357    }
358
359    /**
360     * Returns the {@link ServiceInfo}s of the installed accessibility services.
361     *
362     * @return An unmodifiable list with {@link ServiceInfo}s.
363     *
364     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
365     */
366    @Deprecated
367    public List<ServiceInfo> getAccessibilityServiceList() {
368        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
369        List<ServiceInfo> services = new ArrayList<>();
370        final int infoCount = infos.size();
371        for (int i = 0; i < infoCount; i++) {
372            AccessibilityServiceInfo info = infos.get(i);
373            services.add(info.getResolveInfo().serviceInfo);
374        }
375        return Collections.unmodifiableList(services);
376    }
377
378    /**
379     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
380     *
381     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
382     */
383    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
384        final IAccessibilityManager service;
385        final int userId;
386        synchronized (mLock) {
387            service = getServiceLocked();
388            if (service == null) {
389                return Collections.emptyList();
390            }
391            userId = mUserId;
392        }
393
394        List<AccessibilityServiceInfo> services = null;
395        try {
396            services = service.getInstalledAccessibilityServiceList(userId);
397            if (DEBUG) {
398                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
399            }
400        } catch (RemoteException re) {
401            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
402        }
403        if (services != null) {
404            return Collections.unmodifiableList(services);
405        } else {
406            return Collections.emptyList();
407        }
408    }
409
410    /**
411     * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
412     * for a given feedback type.
413     *
414     * @param feedbackTypeFlags The feedback type flags.
415     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
416     *
417     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
418     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
419     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
420     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
421     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
422     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
423     */
424    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
425            int feedbackTypeFlags) {
426        final IAccessibilityManager service;
427        final int userId;
428        synchronized (mLock) {
429            service = getServiceLocked();
430            if (service == null) {
431                return Collections.emptyList();
432            }
433            userId = mUserId;
434        }
435
436        List<AccessibilityServiceInfo> services = null;
437        try {
438            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
439            if (DEBUG) {
440                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
441            }
442        } catch (RemoteException re) {
443            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
444        }
445        if (services != null) {
446            return Collections.unmodifiableList(services);
447        } else {
448            return Collections.emptyList();
449        }
450    }
451
452    /**
453     * Registers an {@link AccessibilityStateChangeListener} for changes in
454     * the global accessibility state of the system.
455     *
456     * @param listener The listener.
457     * @return True if successfully registered.
458     */
459    public boolean addAccessibilityStateChangeListener(
460            @NonNull AccessibilityStateChangeListener listener) {
461        // Final CopyOnWriteArrayList - no lock needed.
462        return mAccessibilityStateChangeListeners.add(listener);
463    }
464
465    /**
466     * Unregisters an {@link AccessibilityStateChangeListener}.
467     *
468     * @param listener The listener.
469     * @return True if successfully unregistered.
470     */
471    public boolean removeAccessibilityStateChangeListener(
472            @NonNull AccessibilityStateChangeListener listener) {
473        // Final CopyOnWriteArrayList - no lock needed.
474        return mAccessibilityStateChangeListeners.remove(listener);
475    }
476
477    /**
478     * Registers a {@link TouchExplorationStateChangeListener} for changes in
479     * the global touch exploration state of the system.
480     *
481     * @param listener The listener.
482     * @return True if successfully registered.
483     */
484    public boolean addTouchExplorationStateChangeListener(
485            @NonNull TouchExplorationStateChangeListener listener) {
486        // Final CopyOnWriteArrayList - no lock needed.
487        return mTouchExplorationStateChangeListeners.add(listener);
488    }
489
490    /**
491     * Unregisters a {@link TouchExplorationStateChangeListener}.
492     *
493     * @param listener The listener.
494     * @return True if successfully unregistered.
495     */
496    public boolean removeTouchExplorationStateChangeListener(
497            @NonNull TouchExplorationStateChangeListener listener) {
498        // Final CopyOnWriteArrayList - no lock needed.
499        return mTouchExplorationStateChangeListeners.remove(listener);
500    }
501
502    /**
503     * Registers a {@link HighTextContrastChangeListener} for changes in
504     * the global high text contrast state of the system.
505     *
506     * @param listener The listener.
507     * @return True if successfully registered.
508     *
509     * @hide
510     */
511    public boolean addHighTextContrastStateChangeListener(
512            @NonNull HighTextContrastChangeListener listener) {
513        // Final CopyOnWriteArrayList - no lock needed.
514        return mHighTextContrastStateChangeListeners.add(listener);
515    }
516
517    /**
518     * Unregisters a {@link HighTextContrastChangeListener}.
519     *
520     * @param listener The listener.
521     * @return True if successfully unregistered.
522     *
523     * @hide
524     */
525    public boolean removeHighTextContrastStateChangeListener(
526            @NonNull HighTextContrastChangeListener listener) {
527        // Final CopyOnWriteArrayList - no lock needed.
528        return mHighTextContrastStateChangeListeners.remove(listener);
529    }
530
531    /**
532     * Sets the current state and notifies listeners, if necessary.
533     *
534     * @param stateFlags The state flags.
535     */
536    private void setStateLocked(int stateFlags) {
537        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
538        final boolean touchExplorationEnabled =
539                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
540        final boolean highTextContrastEnabled =
541                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
542
543        final boolean wasEnabled = mIsEnabled;
544        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
545        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
546
547        // Ensure listeners get current state from isZzzEnabled() calls.
548        mIsEnabled = enabled;
549        mIsTouchExplorationEnabled = touchExplorationEnabled;
550        mIsHighTextContrastEnabled = highTextContrastEnabled;
551
552        if (wasEnabled != enabled) {
553            mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
554        }
555
556        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
557            mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
558        }
559
560        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
561            mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
562        }
563    }
564
565    /**
566     * Adds an accessibility interaction connection interface for a given window.
567     * @param windowToken The window token to which a connection is added.
568     * @param connection The connection.
569     *
570     * @hide
571     */
572    public int addAccessibilityInteractionConnection(IWindow windowToken,
573            IAccessibilityInteractionConnection connection) {
574        final IAccessibilityManager service;
575        final int userId;
576        synchronized (mLock) {
577            service = getServiceLocked();
578            if (service == null) {
579                return View.NO_ID;
580            }
581            userId = mUserId;
582        }
583        try {
584            return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
585        } catch (RemoteException re) {
586            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
587        }
588        return View.NO_ID;
589    }
590
591    /**
592     * Removed an accessibility interaction connection interface for a given window.
593     * @param windowToken The window token to which a connection is removed.
594     *
595     * @hide
596     */
597    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
598        final IAccessibilityManager service;
599        synchronized (mLock) {
600            service = getServiceLocked();
601            if (service == null) {
602                return;
603            }
604        }
605        try {
606            service.removeAccessibilityInteractionConnection(windowToken);
607        } catch (RemoteException re) {
608            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
609        }
610    }
611
612    private  IAccessibilityManager getServiceLocked() {
613        if (mService == null) {
614            tryConnectToServiceLocked(null);
615        }
616        return mService;
617    }
618
619    private void tryConnectToServiceLocked(IAccessibilityManager service) {
620        if (service == null) {
621            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
622            if (iBinder == null) {
623                return;
624            }
625            service = IAccessibilityManager.Stub.asInterface(iBinder);
626        }
627
628        try {
629            final int stateFlags = service.addClient(mClient, mUserId);
630            setStateLocked(stateFlags);
631            mService = service;
632        } catch (RemoteException re) {
633            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
634        }
635    }
636
637    /**
638     * Notifies the registered {@link AccessibilityStateChangeListener}s.
639     */
640    private void handleNotifyAccessibilityStateChanged() {
641        final boolean isEnabled;
642        synchronized (mLock) {
643            isEnabled = mIsEnabled;
644        }
645        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
646        for (AccessibilityStateChangeListener listener :mAccessibilityStateChangeListeners) {
647            listener.onAccessibilityStateChanged(isEnabled);
648        }
649    }
650
651    /**
652     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
653     */
654    private void handleNotifyTouchExplorationStateChanged() {
655        final boolean isTouchExplorationEnabled;
656        synchronized (mLock) {
657            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
658        }
659        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
660        for (TouchExplorationStateChangeListener listener :mTouchExplorationStateChangeListeners) {
661            listener.onTouchExplorationStateChanged(isTouchExplorationEnabled);
662        }
663    }
664
665    /**
666     * Notifies the registered {@link HighTextContrastChangeListener}s.
667     */
668    private void handleNotifyHighTextContrastStateChanged() {
669        final boolean isHighTextContrastEnabled;
670        synchronized (mLock) {
671            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
672        }
673        // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
674        for (HighTextContrastChangeListener listener : mHighTextContrastStateChangeListeners) {
675            listener.onHighTextContrastStateChanged(isHighTextContrastEnabled);
676        }
677    }
678
679    private final class MyHandler extends Handler {
680        public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
681        public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
682        public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3;
683        public static final int MSG_SET_STATE = 4;
684
685        public MyHandler(Looper looper) {
686            super(looper, null, false);
687        }
688
689        @Override
690        public void handleMessage(Message message) {
691            switch (message.what) {
692                case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
693                    handleNotifyAccessibilityStateChanged();
694                } break;
695
696                case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: {
697                    handleNotifyTouchExplorationStateChanged();
698                } break;
699
700                case MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED: {
701                    handleNotifyHighTextContrastStateChanged();
702                } break;
703
704                case MSG_SET_STATE: {
705                    // See comment at mClient
706                    final int state = message.arg1;
707                    synchronized (mLock) {
708                        setStateLocked(state);
709                    }
710                } break;
711            }
712        }
713    }
714}
715