AccessibilityManager.java revision 9af1378c82c2e39c40383af117c568257152eee9
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 static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
20
21import android.Manifest;
22import android.accessibilityservice.AccessibilityServiceInfo;
23import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
24import android.annotation.NonNull;
25import android.annotation.Nullable;
26import android.annotation.SdkConstant;
27import android.annotation.SystemService;
28import android.content.ComponentName;
29import android.content.Context;
30import android.content.pm.PackageManager;
31import android.content.pm.ServiceInfo;
32import android.content.res.Resources;
33import android.os.Binder;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.Message;
38import android.os.Process;
39import android.os.RemoteException;
40import android.os.ServiceManager;
41import android.os.SystemClock;
42import android.os.UserHandle;
43import android.util.ArrayMap;
44import android.util.Log;
45import android.util.SparseArray;
46import android.view.IWindow;
47import android.view.View;
48import android.view.accessibility.AccessibilityEvent.EventType;
49
50import com.android.internal.annotations.VisibleForTesting;
51import com.android.internal.util.IntPair;
52
53import java.util.ArrayList;
54import java.util.Collections;
55import java.util.List;
56
57/**
58 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
59 * and provides facilities for querying the accessibility state of the system.
60 * Accessibility events are generated when something notable happens in the user interface,
61 * for example an {@link android.app.Activity} starts, the focus or selection of a
62 * {@link android.view.View} changes etc. Parties interested in handling accessibility
63 * events implement and register an accessibility service which extends
64 * {@link android.accessibilityservice.AccessibilityService}.
65 *
66 * @see AccessibilityEvent
67 * @see AccessibilityNodeInfo
68 * @see android.accessibilityservice.AccessibilityService
69 * @see Context#getSystemService
70 * @see Context#ACCESSIBILITY_SERVICE
71 */
72@SystemService(Context.ACCESSIBILITY_SERVICE)
73public final class AccessibilityManager {
74    private static final boolean DEBUG = false;
75
76    private static final String LOG_TAG = "AccessibilityManager";
77
78    /** @hide */
79    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
80
81    /** @hide */
82    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
83
84    /** @hide */
85    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
86
87    /** @hide */
88    public static final int DALTONIZER_DISABLED = -1;
89
90    /** @hide */
91    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
92
93    /** @hide */
94    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
95
96    /** @hide */
97    public static final int AUTOCLICK_DELAY_DEFAULT = 600;
98
99    /**
100     * Activity action: Launch UI to manage which accessibility service or feature is assigned
101     * to the navigation bar Accessibility button.
102     * <p>
103     * Input: Nothing.
104     * </p>
105     * <p>
106     * Output: Nothing.
107     * </p>
108     *
109     * @hide
110     */
111    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
112    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
113            "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
114
115    static final Object sInstanceSync = new Object();
116
117    private static AccessibilityManager sInstance;
118
119    private final Object mLock = new Object();
120
121    private IAccessibilityManager mService;
122
123    final int mUserId;
124
125    final Handler mHandler;
126
127    final Handler.Callback mCallback;
128
129    boolean mIsEnabled;
130
131    int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
132
133    boolean mIsTouchExplorationEnabled;
134
135    boolean mIsHighTextContrastEnabled;
136
137    AccessibilityPolicy mAccessibilityPolicy;
138
139    private final ArrayMap<AccessibilityStateChangeListener, Handler>
140            mAccessibilityStateChangeListeners = new ArrayMap<>();
141
142    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
143            mTouchExplorationStateChangeListeners = new ArrayMap<>();
144
145    private final ArrayMap<HighTextContrastChangeListener, Handler>
146            mHighTextContrastStateChangeListeners = new ArrayMap<>();
147
148    private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
149            mServicesStateChangeListeners = new ArrayMap<>();
150
151    /**
152     * Map from a view's accessibility id to the list of request preparers set for that view
153     */
154    private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
155
156    /**
157     * Listener for the system accessibility state. To listen for changes to the
158     * accessibility state on the device, implement this interface and register
159     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
160     */
161    public interface AccessibilityStateChangeListener {
162
163        /**
164         * Called when the accessibility enabled state changes.
165         *
166         * @param enabled Whether accessibility is enabled.
167         */
168        void onAccessibilityStateChanged(boolean enabled);
169    }
170
171    /**
172     * Listener for the system touch exploration state. To listen for changes to
173     * the touch exploration state on the device, implement this interface and
174     * register it with the system by calling
175     * {@link #addTouchExplorationStateChangeListener}.
176     */
177    public interface TouchExplorationStateChangeListener {
178
179        /**
180         * Called when the touch exploration enabled state changes.
181         *
182         * @param enabled Whether touch exploration is enabled.
183         */
184        void onTouchExplorationStateChanged(boolean enabled);
185    }
186
187    /**
188     * Listener for changes to the state of accessibility services. Changes include services being
189     * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
190     * {@see #addAccessibilityServicesStateChangeListener}.
191     *
192     * @hide
193     */
194    public interface AccessibilityServicesStateChangeListener {
195
196        /**
197         * Called when the state of accessibility services changes.
198         *
199         * @param manager The manager that is calling back
200         */
201        void onAccessibilityServicesStateChanged(AccessibilityManager manager);
202    }
203
204    /**
205     * Listener for the system high text contrast state. To listen for changes to
206     * the high text contrast state on the device, implement this interface and
207     * register it with the system by calling
208     * {@link #addHighTextContrastStateChangeListener}.
209     *
210     * @hide
211     */
212    public interface HighTextContrastChangeListener {
213
214        /**
215         * Called when the high text contrast enabled state changes.
216         *
217         * @param enabled Whether high text contrast is enabled.
218         */
219        void onHighTextContrastStateChanged(boolean enabled);
220    }
221
222    /**
223     * Policy to inject behavior into the accessibility manager.
224     *
225     * @hide
226     */
227    public interface AccessibilityPolicy {
228        /**
229         * Checks whether accessibility is enabled.
230         *
231         * @param accessibilityEnabled Whether the accessibility layer is enabled.
232         * @return whether accessibility is enabled.
233         */
234        boolean isEnabled(boolean accessibilityEnabled);
235
236        /**
237         * Notifies the policy for an accessibility event.
238         *
239         * @param event The event.
240         * @param accessibilityEnabled Whether the accessibility layer is enabled.
241         * @param relevantEventTypes The events relevant events.
242         * @return The event to dispatch or null.
243         */
244        @Nullable AccessibilityEvent onAccessibilityEvent(@NonNull AccessibilityEvent event,
245                boolean accessibilityEnabled, @EventType int relevantEventTypes);
246
247        /**
248         * Gets the list of relevant events.
249         *
250         * @param relevantEventTypes The relevant events.
251         * @return The relevant events to report.
252         */
253        @EventType int getRelevantEventTypes(@EventType int relevantEventTypes);
254
255        /**
256         * Gets the list of installed services to report.
257         *
258         * @param installedService The installed services.
259         * @return The services to report.
260         */
261        @NonNull List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
262                @Nullable List<AccessibilityServiceInfo> installedService);
263
264        /**
265         * Gets the list of enabled accessibility services.
266         *
267         * @param feedbackTypeFlags The feedback type to query for.
268         * @param enabledService The enabled services.
269         * @return The services to report.
270         */
271        @Nullable List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
272                @FeedbackType int feedbackTypeFlags,
273                @Nullable List<AccessibilityServiceInfo> enabledService);
274    }
275
276    private final IAccessibilityManagerClient.Stub mClient =
277            new IAccessibilityManagerClient.Stub() {
278        @Override
279        public void setState(int state) {
280            // We do not want to change this immediately as the application may
281            // have already checked that accessibility is on and fired an event,
282            // that is now propagating up the view tree, Hence, if accessibility
283            // is now off an exception will be thrown. We want to have the exception
284            // enforcement to guard against apps that fire unnecessary accessibility
285            // events when accessibility is off.
286            mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
287        }
288
289        @Override
290        public void notifyServicesStateChanged() {
291            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
292            synchronized (mLock) {
293                if (mServicesStateChangeListeners.isEmpty()) {
294                    return;
295                }
296                listeners = new ArrayMap<>(mServicesStateChangeListeners);
297            }
298
299            int numListeners = listeners.size();
300            for (int i = 0; i < numListeners; i++) {
301                final AccessibilityServicesStateChangeListener listener =
302                        mServicesStateChangeListeners.keyAt(i);
303                mServicesStateChangeListeners.valueAt(i).post(() -> listener
304                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
305            }
306        }
307
308        @Override
309        public void setRelevantEventTypes(int eventTypes) {
310            mRelevantEventTypes = eventTypes;
311        }
312    };
313
314    /**
315     * Get an AccessibilityManager instance (create one if necessary).
316     *
317     * @param context Context in which this manager operates.
318     *
319     * @hide
320     */
321    public static AccessibilityManager getInstance(Context context) {
322        synchronized (sInstanceSync) {
323            if (sInstance == null) {
324                final int userId;
325                if (Binder.getCallingUid() == Process.SYSTEM_UID
326                        || context.checkCallingOrSelfPermission(
327                                Manifest.permission.INTERACT_ACROSS_USERS)
328                                        == PackageManager.PERMISSION_GRANTED
329                        || context.checkCallingOrSelfPermission(
330                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
331                                        == PackageManager.PERMISSION_GRANTED) {
332                    userId = UserHandle.USER_CURRENT;
333                } else {
334                    userId = context.getUserId();
335                }
336                sInstance = new AccessibilityManager(context, null, userId);
337            }
338        }
339        return sInstance;
340    }
341
342    /**
343     * Create an instance.
344     *
345     * @param context A {@link Context}.
346     * @param service An interface to the backing service.
347     * @param userId User id under which to run.
348     *
349     * @hide
350     */
351    public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
352        // Constructor can't be chained because we can't create an instance of an inner class
353        // before calling another constructor.
354        mCallback = new MyCallback();
355        mHandler = new Handler(context.getMainLooper(), mCallback);
356        mUserId = userId;
357        synchronized (mLock) {
358            tryConnectToServiceLocked(service);
359        }
360    }
361
362    /**
363     * Create an instance.
364     *
365     * @param handler The handler to use
366     * @param service An interface to the backing service.
367     * @param userId User id under which to run.
368     *
369     * @hide
370     */
371    public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
372        mCallback = new MyCallback();
373        mHandler = handler;
374        mUserId = userId;
375        synchronized (mLock) {
376            tryConnectToServiceLocked(service);
377        }
378    }
379
380    /**
381     * @hide
382     */
383    public IAccessibilityManagerClient getClient() {
384        return mClient;
385    }
386
387    /**
388     * @hide
389     */
390    @VisibleForTesting
391    public Handler.Callback getCallback() {
392        return mCallback;
393    }
394
395    /**
396     * Returns if the accessibility in the system is enabled.
397     *
398     * @return True if accessibility is enabled, false otherwise.
399     */
400    public boolean isEnabled() {
401        synchronized (mLock) {
402            return mIsEnabled || (mAccessibilityPolicy != null
403                    && mAccessibilityPolicy.isEnabled(mIsEnabled));
404        }
405    }
406
407    /**
408     * Returns if the touch exploration in the system is enabled.
409     *
410     * @return True if touch exploration is enabled, false otherwise.
411     */
412    public boolean isTouchExplorationEnabled() {
413        synchronized (mLock) {
414            IAccessibilityManager service = getServiceLocked();
415            if (service == null) {
416                return false;
417            }
418            return mIsTouchExplorationEnabled;
419        }
420    }
421
422    /**
423     * Returns if the high text contrast in the system is enabled.
424     * <p>
425     * <strong>Note:</strong> You need to query this only if you application is
426     * doing its own rendering and does not rely on the platform rendering pipeline.
427     * </p>
428     *
429     * @return True if high text contrast is enabled, false otherwise.
430     *
431     * @hide
432     */
433    public boolean isHighTextContrastEnabled() {
434        synchronized (mLock) {
435            IAccessibilityManager service = getServiceLocked();
436            if (service == null) {
437                return false;
438            }
439            return mIsHighTextContrastEnabled;
440        }
441    }
442
443    /**
444     * Sends an {@link AccessibilityEvent}.
445     *
446     * @param event The event to send.
447     *
448     * @throws IllegalStateException if accessibility is not enabled.
449     *
450     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
451     * events is through calling
452     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
453     * instead of this method to allow predecessors to augment/filter events sent by
454     * their descendants.
455     */
456    public void sendAccessibilityEvent(AccessibilityEvent event) {
457        final IAccessibilityManager service;
458        final int userId;
459        final AccessibilityEvent dispatchedEvent;
460        synchronized (mLock) {
461            service = getServiceLocked();
462            if (service == null) {
463                return;
464            }
465            event.setEventTime(SystemClock.uptimeMillis());
466            if (mAccessibilityPolicy != null) {
467                dispatchedEvent = mAccessibilityPolicy.onAccessibilityEvent(event,
468                        mIsEnabled, mRelevantEventTypes);
469                if (dispatchedEvent == null) {
470                    return;
471                }
472            } else {
473                dispatchedEvent = event;
474            }
475            if (!isEnabled()) {
476                Looper myLooper = Looper.myLooper();
477                if (myLooper == Looper.getMainLooper()) {
478                    throw new IllegalStateException(
479                            "Accessibility off. Did you forget to check that?");
480                } else {
481                    // If we're not running on the thread with the main looper, it's possible for
482                    // the state of accessibility to change between checking isEnabled and
483                    // calling this method. So just log the error rather than throwing the
484                    // exception.
485                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
486                    return;
487                }
488            }
489            if ((dispatchedEvent.getEventType() & mRelevantEventTypes) == 0) {
490                if (DEBUG) {
491                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + dispatchedEvent
492                            + " that is not among "
493                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
494                }
495                return;
496            }
497            userId = mUserId;
498        }
499        try {
500            // it is possible that this manager is in the same process as the service but
501            // client using it is called through Binder from another process. Example: MMS
502            // app adds a SMS notification and the NotificationManagerService calls this method
503            long identityToken = Binder.clearCallingIdentity();
504            try {
505                service.sendAccessibilityEvent(dispatchedEvent, userId);
506            } finally {
507                Binder.restoreCallingIdentity(identityToken);
508            }
509            if (DEBUG) {
510                Log.i(LOG_TAG, dispatchedEvent + " sent");
511            }
512        } catch (RemoteException re) {
513            Log.e(LOG_TAG, "Error during sending " + dispatchedEvent + " ", re);
514        } finally {
515            if (event != dispatchedEvent) {
516                event.recycle();
517            }
518            dispatchedEvent.recycle();
519        }
520    }
521
522    /**
523     * Requests feedback interruption from all accessibility services.
524     */
525    public void interrupt() {
526        final IAccessibilityManager service;
527        final int userId;
528        synchronized (mLock) {
529            service = getServiceLocked();
530            if (service == null) {
531                return;
532            }
533            if (!isEnabled()) {
534                Looper myLooper = Looper.myLooper();
535                if (myLooper == Looper.getMainLooper()) {
536                    throw new IllegalStateException(
537                            "Accessibility off. Did you forget to check that?");
538                } else {
539                    // If we're not running on the thread with the main looper, it's possible for
540                    // the state of accessibility to change between checking isEnabled and
541                    // calling this method. So just log the error rather than throwing the
542                    // exception.
543                    Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
544                    return;
545                }
546            }
547            userId = mUserId;
548        }
549        try {
550            service.interrupt(userId);
551            if (DEBUG) {
552                Log.i(LOG_TAG, "Requested interrupt from all services");
553            }
554        } catch (RemoteException re) {
555            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
556        }
557    }
558
559    /**
560     * Returns the {@link ServiceInfo}s of the installed accessibility services.
561     *
562     * @return An unmodifiable list with {@link ServiceInfo}s.
563     *
564     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
565     */
566    @Deprecated
567    public List<ServiceInfo> getAccessibilityServiceList() {
568        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
569        List<ServiceInfo> services = new ArrayList<>();
570        final int infoCount = infos.size();
571        for (int i = 0; i < infoCount; i++) {
572            AccessibilityServiceInfo info = infos.get(i);
573            services.add(info.getResolveInfo().serviceInfo);
574        }
575        return Collections.unmodifiableList(services);
576    }
577
578    /**
579     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
580     *
581     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
582     */
583    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
584        final IAccessibilityManager service;
585        final int userId;
586        synchronized (mLock) {
587            service = getServiceLocked();
588            if (service == null) {
589                return Collections.emptyList();
590            }
591            userId = mUserId;
592        }
593
594        List<AccessibilityServiceInfo> services = null;
595        try {
596            services = service.getInstalledAccessibilityServiceList(userId);
597            if (DEBUG) {
598                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
599            }
600        } catch (RemoteException re) {
601            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
602        }
603        if (mAccessibilityPolicy != null) {
604            services = mAccessibilityPolicy.getInstalledAccessibilityServiceList(services);
605        }
606        if (services != null) {
607            return Collections.unmodifiableList(services);
608        } else {
609            return Collections.emptyList();
610        }
611    }
612
613    /**
614     * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
615     * for a given feedback type.
616     *
617     * @param feedbackTypeFlags The feedback type flags.
618     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
619     *
620     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
621     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
622     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
623     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
624     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
625     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
626     */
627    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
628            int feedbackTypeFlags) {
629        final IAccessibilityManager service;
630        final int userId;
631        synchronized (mLock) {
632            service = getServiceLocked();
633            if (service == null) {
634                return Collections.emptyList();
635            }
636            userId = mUserId;
637        }
638
639        List<AccessibilityServiceInfo> services = null;
640        try {
641            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
642            if (DEBUG) {
643                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
644            }
645        } catch (RemoteException re) {
646            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
647        }
648        if (mAccessibilityPolicy != null) {
649            services = mAccessibilityPolicy.getEnabledAccessibilityServiceList(
650                    feedbackTypeFlags, services);
651        }
652        if (services != null) {
653            return Collections.unmodifiableList(services);
654        } else {
655            return Collections.emptyList();
656        }
657    }
658
659    /**
660     * Registers an {@link AccessibilityStateChangeListener} for changes in
661     * the global accessibility state of the system. Equivalent to calling
662     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
663     * with a null handler.
664     *
665     * @param listener The listener.
666     * @return Always returns {@code true}.
667     */
668    public boolean addAccessibilityStateChangeListener(
669            @NonNull AccessibilityStateChangeListener listener) {
670        addAccessibilityStateChangeListener(listener, null);
671        return true;
672    }
673
674    /**
675     * Registers an {@link AccessibilityStateChangeListener} for changes in
676     * the global accessibility state of the system. If the listener has already been registered,
677     * the handler used to call it back is updated.
678     *
679     * @param listener The listener.
680     * @param handler The handler on which the listener should be called back, or {@code null}
681     *                for a callback on the process's main handler.
682     */
683    public void addAccessibilityStateChangeListener(
684            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
685        synchronized (mLock) {
686            mAccessibilityStateChangeListeners
687                    .put(listener, (handler == null) ? mHandler : handler);
688        }
689    }
690
691    /**
692     * Unregisters an {@link AccessibilityStateChangeListener}.
693     *
694     * @param listener The listener.
695     * @return True if the listener was previously registered.
696     */
697    public boolean removeAccessibilityStateChangeListener(
698            @NonNull AccessibilityStateChangeListener listener) {
699        synchronized (mLock) {
700            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
701            mAccessibilityStateChangeListeners.remove(listener);
702            return (index >= 0);
703        }
704    }
705
706    /**
707     * Registers a {@link TouchExplorationStateChangeListener} for changes in
708     * the global touch exploration state of the system. Equivalent to calling
709     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
710     * with a null handler.
711     *
712     * @param listener The listener.
713     * @return Always returns {@code true}.
714     */
715    public boolean addTouchExplorationStateChangeListener(
716            @NonNull TouchExplorationStateChangeListener listener) {
717        addTouchExplorationStateChangeListener(listener, null);
718        return true;
719    }
720
721    /**
722     * Registers an {@link TouchExplorationStateChangeListener} for changes in
723     * the global touch exploration state of the system. If the listener has already been
724     * registered, the handler used to call it back is updated.
725     *
726     * @param listener The listener.
727     * @param handler The handler on which the listener should be called back, or {@code null}
728     *                for a callback on the process's main handler.
729     */
730    public void addTouchExplorationStateChangeListener(
731            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
732        synchronized (mLock) {
733            mTouchExplorationStateChangeListeners
734                    .put(listener, (handler == null) ? mHandler : handler);
735        }
736    }
737
738    /**
739     * Unregisters a {@link TouchExplorationStateChangeListener}.
740     *
741     * @param listener The listener.
742     * @return True if listener was previously registered.
743     */
744    public boolean removeTouchExplorationStateChangeListener(
745            @NonNull TouchExplorationStateChangeListener listener) {
746        synchronized (mLock) {
747            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
748            mTouchExplorationStateChangeListeners.remove(listener);
749            return (index >= 0);
750        }
751    }
752
753    /**
754     * Registers a {@link AccessibilityServicesStateChangeListener}.
755     *
756     * @param listener The listener.
757     * @param handler The handler on which the listener should be called back, or {@code null}
758     *                for a callback on the process's main handler.
759     * @hide
760     */
761    public void addAccessibilityServicesStateChangeListener(
762            @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
763        synchronized (mLock) {
764            mServicesStateChangeListeners
765                    .put(listener, (handler == null) ? mHandler : handler);
766        }
767    }
768
769    /**
770     * Unregisters a {@link AccessibilityServicesStateChangeListener}.
771     *
772     * @param listener The listener.
773     *
774     * @hide
775     */
776    public void removeAccessibilityServicesStateChangeListener(
777            @NonNull AccessibilityServicesStateChangeListener listener) {
778        synchronized (mLock) {
779            mServicesStateChangeListeners.remove(listener);
780        }
781    }
782
783    /**
784     * Registers a {@link AccessibilityRequestPreparer}.
785     */
786    public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
787        if (mRequestPreparerLists == null) {
788            mRequestPreparerLists = new SparseArray<>(1);
789        }
790        int id = preparer.getView().getAccessibilityViewId();
791        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
792        if (requestPreparerList == null) {
793            requestPreparerList = new ArrayList<>(1);
794            mRequestPreparerLists.put(id, requestPreparerList);
795        }
796        requestPreparerList.add(preparer);
797    }
798
799    /**
800     * Unregisters a {@link AccessibilityRequestPreparer}.
801     */
802    public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
803        if (mRequestPreparerLists == null) {
804            return;
805        }
806        int viewId = preparer.getView().getAccessibilityViewId();
807        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
808        if (requestPreparerList != null) {
809            requestPreparerList.remove(preparer);
810            if (requestPreparerList.isEmpty()) {
811                mRequestPreparerLists.remove(viewId);
812            }
813        }
814    }
815
816    /**
817     * Get the preparers that are registered for an accessibility ID
818     *
819     * @param id The ID of interest
820     * @return The list of preparers, or {@code null} if there are none.
821     *
822     * @hide
823     */
824    public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
825        if (mRequestPreparerLists == null) {
826            return null;
827        }
828        return mRequestPreparerLists.get(id);
829    }
830
831    /**
832     * Registers a {@link HighTextContrastChangeListener} for changes in
833     * the global high text contrast state of the system.
834     *
835     * @param listener The listener.
836     *
837     * @hide
838     */
839    public void addHighTextContrastStateChangeListener(
840            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
841        synchronized (mLock) {
842            mHighTextContrastStateChangeListeners
843                    .put(listener, (handler == null) ? mHandler : handler);
844        }
845    }
846
847    /**
848     * Unregisters a {@link HighTextContrastChangeListener}.
849     *
850     * @param listener The listener.
851     *
852     * @hide
853     */
854    public void removeHighTextContrastStateChangeListener(
855            @NonNull HighTextContrastChangeListener listener) {
856        synchronized (mLock) {
857            mHighTextContrastStateChangeListeners.remove(listener);
858        }
859    }
860
861    /**
862     * Sets the {@link AccessibilityPolicy} controlling this manager.
863     *
864     * @param policy The policy.
865     *
866     * @hide
867     */
868    public void setAccessibilityPolicy(@Nullable AccessibilityPolicy policy) {
869        synchronized (mLock) {
870            mAccessibilityPolicy = policy;
871        }
872    }
873
874    /**
875     * Check if the accessibility volume stream is active.
876     *
877     * @return True if accessibility volume is active (i.e. some service has requested it). False
878     * otherwise.
879     * @hide
880     */
881    public boolean isAccessibilityVolumeStreamActive() {
882        List<AccessibilityServiceInfo> serviceInfos =
883                getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
884        for (int i = 0; i < serviceInfos.size(); i++) {
885            if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
886                return true;
887            }
888        }
889        return false;
890    }
891
892    /**
893     * Report a fingerprint gesture to accessibility. Only available for the system process.
894     *
895     * @param keyCode The key code of the gesture
896     * @return {@code true} if accessibility consumes the event. {@code false} if not.
897     * @hide
898     */
899    public boolean sendFingerprintGesture(int keyCode) {
900        final IAccessibilityManager service;
901        synchronized (mLock) {
902            service = getServiceLocked();
903            if (service == null) {
904                return false;
905            }
906        }
907        try {
908            return service.sendFingerprintGesture(keyCode);
909        } catch (RemoteException e) {
910            return false;
911        }
912    }
913
914    /**
915     * Sets the current state and notifies listeners, if necessary.
916     *
917     * @param stateFlags The state flags.
918     */
919    private void setStateLocked(int stateFlags) {
920        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
921        final boolean touchExplorationEnabled =
922                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
923        final boolean highTextContrastEnabled =
924                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
925
926        final boolean wasEnabled = isEnabled();
927        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
928        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
929
930        // Ensure listeners get current state from isZzzEnabled() calls.
931        mIsEnabled = enabled;
932        mIsTouchExplorationEnabled = touchExplorationEnabled;
933        mIsHighTextContrastEnabled = highTextContrastEnabled;
934
935        if (wasEnabled != isEnabled()) {
936            notifyAccessibilityStateChanged();
937        }
938
939        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
940            notifyTouchExplorationStateChanged();
941        }
942
943        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
944            notifyHighTextContrastStateChanged();
945        }
946    }
947
948    /**
949     * Find an installed service with the specified {@link ComponentName}.
950     *
951     * @param componentName The name to match to the service.
952     *
953     * @return The info corresponding to the installed service, or {@code null} if no such service
954     * is installed.
955     * @hide
956     */
957    public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
958            ComponentName componentName) {
959        final List<AccessibilityServiceInfo> installedServiceInfos =
960                getInstalledAccessibilityServiceList();
961        if ((installedServiceInfos == null) || (componentName == null)) {
962            return null;
963        }
964        for (int i = 0; i < installedServiceInfos.size(); i++) {
965            if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
966                return installedServiceInfos.get(i);
967            }
968        }
969        return null;
970    }
971
972    /**
973     * Adds an accessibility interaction connection interface for a given window.
974     * @param windowToken The window token to which a connection is added.
975     * @param connection The connection.
976     *
977     * @hide
978     */
979    public int addAccessibilityInteractionConnection(IWindow windowToken,
980            String packageName, IAccessibilityInteractionConnection connection) {
981        final IAccessibilityManager service;
982        final int userId;
983        synchronized (mLock) {
984            service = getServiceLocked();
985            if (service == null) {
986                return View.NO_ID;
987            }
988            userId = mUserId;
989        }
990        try {
991            return service.addAccessibilityInteractionConnection(windowToken, connection,
992                    packageName, userId);
993        } catch (RemoteException re) {
994            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
995        }
996        return View.NO_ID;
997    }
998
999    /**
1000     * Removed an accessibility interaction connection interface for a given window.
1001     * @param windowToken The window token to which a connection is removed.
1002     *
1003     * @hide
1004     */
1005    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
1006        final IAccessibilityManager service;
1007        synchronized (mLock) {
1008            service = getServiceLocked();
1009            if (service == null) {
1010                return;
1011            }
1012        }
1013        try {
1014            service.removeAccessibilityInteractionConnection(windowToken);
1015        } catch (RemoteException re) {
1016            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
1017        }
1018    }
1019
1020    /**
1021     * Perform the accessibility shortcut if the caller has permission.
1022     *
1023     * @hide
1024     */
1025    public void performAccessibilityShortcut() {
1026        final IAccessibilityManager service;
1027        synchronized (mLock) {
1028            service = getServiceLocked();
1029            if (service == null) {
1030                return;
1031            }
1032        }
1033        try {
1034            service.performAccessibilityShortcut();
1035        } catch (RemoteException re) {
1036            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
1037        }
1038    }
1039
1040    /**
1041     * Notifies that the accessibility button in the system's navigation area has been clicked
1042     *
1043     * @hide
1044     */
1045    public void notifyAccessibilityButtonClicked() {
1046        final IAccessibilityManager service;
1047        synchronized (mLock) {
1048            service = getServiceLocked();
1049            if (service == null) {
1050                return;
1051            }
1052        }
1053        try {
1054            service.notifyAccessibilityButtonClicked();
1055        } catch (RemoteException re) {
1056            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
1057        }
1058    }
1059
1060    /**
1061     * Notifies that the visibility of the accessibility button in the system's navigation area
1062     * has changed.
1063     *
1064     * @param shown {@code true} if the accessibility button is visible within the system
1065     *                  navigation area, {@code false} otherwise
1066     * @hide
1067     */
1068    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
1069        final IAccessibilityManager service;
1070        synchronized (mLock) {
1071            service = getServiceLocked();
1072            if (service == null) {
1073                return;
1074            }
1075        }
1076        try {
1077            service.notifyAccessibilityButtonVisibilityChanged(shown);
1078        } catch (RemoteException re) {
1079            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
1080        }
1081    }
1082
1083    /**
1084     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
1085     * window. Intended for use by the System UI only.
1086     *
1087     * @param connection The connection to handle the actions. Set to {@code null} to avoid
1088     * affecting the actions.
1089     *
1090     * @hide
1091     */
1092    public void setPictureInPictureActionReplacingConnection(
1093            @Nullable IAccessibilityInteractionConnection connection) {
1094        final IAccessibilityManager service;
1095        synchronized (mLock) {
1096            service = getServiceLocked();
1097            if (service == null) {
1098                return;
1099            }
1100        }
1101        try {
1102            service.setPictureInPictureActionReplacingConnection(connection);
1103        } catch (RemoteException re) {
1104            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
1105        }
1106    }
1107
1108    private IAccessibilityManager getServiceLocked() {
1109        if (mService == null) {
1110            tryConnectToServiceLocked(null);
1111        }
1112        return mService;
1113    }
1114
1115    private void tryConnectToServiceLocked(IAccessibilityManager service) {
1116        if (service == null) {
1117            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
1118            if (iBinder == null) {
1119                return;
1120            }
1121            service = IAccessibilityManager.Stub.asInterface(iBinder);
1122        }
1123
1124        try {
1125            final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
1126            setStateLocked(IntPair.first(userStateAndRelevantEvents));
1127            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
1128            mService = service;
1129        } catch (RemoteException re) {
1130            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
1131        }
1132    }
1133
1134    /**
1135     * Notifies the registered {@link AccessibilityStateChangeListener}s.
1136     */
1137    private void notifyAccessibilityStateChanged() {
1138        final boolean isEnabled;
1139        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
1140        synchronized (mLock) {
1141            if (mAccessibilityStateChangeListeners.isEmpty()) {
1142                return;
1143            }
1144            isEnabled = isEnabled();
1145            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
1146        }
1147
1148        final int numListeners = listeners.size();
1149        for (int i = 0; i < numListeners; i++) {
1150            final AccessibilityStateChangeListener listener = listeners.keyAt(i);
1151            listeners.valueAt(i).post(() ->
1152                    listener.onAccessibilityStateChanged(isEnabled));
1153        }
1154    }
1155
1156    /**
1157     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
1158     */
1159    private void notifyTouchExplorationStateChanged() {
1160        final boolean isTouchExplorationEnabled;
1161        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
1162        synchronized (mLock) {
1163            if (mTouchExplorationStateChangeListeners.isEmpty()) {
1164                return;
1165            }
1166            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
1167            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
1168        }
1169
1170        final int numListeners = listeners.size();
1171        for (int i = 0; i < numListeners; i++) {
1172            final TouchExplorationStateChangeListener listener = listeners.keyAt(i);
1173            listeners.valueAt(i).post(() ->
1174                    listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
1175        }
1176    }
1177
1178    /**
1179     * Notifies the registered {@link HighTextContrastChangeListener}s.
1180     */
1181    private void notifyHighTextContrastStateChanged() {
1182        final boolean isHighTextContrastEnabled;
1183        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
1184        synchronized (mLock) {
1185            if (mHighTextContrastStateChangeListeners.isEmpty()) {
1186                return;
1187            }
1188            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
1189            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
1190        }
1191
1192        final int numListeners = listeners.size();
1193        for (int i = 0; i < numListeners; i++) {
1194            final HighTextContrastChangeListener listener = listeners.keyAt(i);
1195            listeners.valueAt(i).post(() ->
1196                    listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
1197        }
1198    }
1199
1200    /**
1201     * Determines if the accessibility button within the system navigation area is supported.
1202     *
1203     * @return {@code true} if the accessibility button is supported on this device,
1204     * {@code false} otherwise
1205     */
1206    public static boolean isAccessibilityButtonSupported() {
1207        final Resources res = Resources.getSystem();
1208        return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
1209    }
1210
1211    private final class MyCallback implements Handler.Callback {
1212        public static final int MSG_SET_STATE = 1;
1213
1214        @Override
1215        public boolean handleMessage(Message message) {
1216            switch (message.what) {
1217                case MSG_SET_STATE: {
1218                    // See comment at mClient
1219                    final int state = message.arg1;
1220                    synchronized (mLock) {
1221                        setStateLocked(state);
1222                    }
1223                } break;
1224            }
1225            return true;
1226        }
1227    }
1228}
1229