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