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