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.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.SdkConstant;
27 import android.annotation.SystemService;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ServiceInfo;
32 import android.content.res.Resources;
33 import android.os.Binder;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.Message;
38 import android.os.Process;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.SystemClock;
42 import android.os.UserHandle;
43 import android.util.ArrayMap;
44 import android.util.Log;
45 import android.util.SparseArray;
46 import android.view.IWindow;
47 import android.view.View;
48 import android.view.accessibility.AccessibilityEvent.EventType;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.util.IntPair;
52 
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import 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)
73 public 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          */
onAccessibilityStateChanged(boolean enabled)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          */
onTouchExplorationStateChanged(boolean enabled)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          */
onAccessibilityServicesStateChanged(AccessibilityManager manager)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          */
onHighTextContrastStateChanged(boolean enabled)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          */
isEnabled(boolean accessibilityEnabled)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          */
onAccessibilityEvent(@onNull AccessibilityEvent event, boolean accessibilityEnabled, @EventType int relevantEventTypes)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          */
getRelevantEventTypes(@ventType int relevantEventTypes)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          */
getInstalledAccessibilityServiceList( @ullable List<AccessibilityServiceInfo> installedService)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          */
getEnabledAccessibilityServiceList( @eedbackType int feedbackTypeFlags, @Nullable List<AccessibilityServiceInfo> enabledService)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      */
getInstance(Context context)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      */
AccessibilityManager(Context context, IAccessibilityManager service, int userId)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      */
AccessibilityManager(Handler handler, IAccessibilityManager service, int userId)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      */
getClient()383     public IAccessibilityManagerClient getClient() {
384         return mClient;
385     }
386 
387     /**
388      * @hide
389      */
390     @VisibleForTesting
getCallback()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      */
isEnabled()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      */
isTouchExplorationEnabled()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      */
isHighTextContrastEnabled()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      */
sendAccessibilityEvent(AccessibilityEvent event)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      */
interrupt()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
getAccessibilityServiceList()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      */
getInstalledAccessibilityServiceList()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      */
getEnabledAccessibilityServiceList( int feedbackTypeFlags)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      */
addAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)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      */
addAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener, @Nullable Handler handler)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      */
removeAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)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      */
addTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)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      */
addTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener, @Nullable Handler handler)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      */
removeTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)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      */
addAccessibilityServicesStateChangeListener( @onNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler)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      */
removeAccessibilityServicesStateChangeListener( @onNull AccessibilityServicesStateChangeListener listener)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      */
addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer)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      */
removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer)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      */
getRequestPreparersForAccessibilityId(int id)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      */
addHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener, @Nullable Handler handler)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      */
removeHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener)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      */
setAccessibilityPolicy(@ullable AccessibilityPolicy policy)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      */
isAccessibilityVolumeStreamActive()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      */
sendFingerprintGesture(int keyCode)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      */
setStateLocked(int stateFlags)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      */
getInstalledServiceInfoWithComponentName( ComponentName componentName)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      */
addAccessibilityInteractionConnection(IWindow windowToken, String packageName, IAccessibilityInteractionConnection connection)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      */
removeAccessibilityInteractionConnection(IWindow windowToken)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      */
performAccessibilityShortcut()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      */
notifyAccessibilityButtonClicked()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      */
notifyAccessibilityButtonVisibilityChanged(boolean shown)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      */
setPictureInPictureActionReplacingConnection( @ullable IAccessibilityInteractionConnection connection)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 
getServiceLocked()1108     private IAccessibilityManager getServiceLocked() {
1109         if (mService == null) {
1110             tryConnectToServiceLocked(null);
1111         }
1112         return mService;
1113     }
1114 
tryConnectToServiceLocked(IAccessibilityManager service)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      */
notifyAccessibilityStateChanged()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      */
notifyTouchExplorationStateChanged()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      */
notifyHighTextContrastStateChanged()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      */
isAccessibilityButtonSupported()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
handleMessage(Message message)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