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 android.Manifest;
20 import android.accessibilityservice.AccessibilityServiceInfo;
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ServiceInfo;
25 import android.os.Binder;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.util.Log;
36 import android.view.IWindow;
37 import android.view.View;
38 
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.concurrent.CopyOnWriteArrayList;
43 
44 /**
45  * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
46  * and provides facilities for querying the accessibility state of the system.
47  * Accessibility events are generated when something notable happens in the user interface,
48  * for example an {@link android.app.Activity} starts, the focus or selection of a
49  * {@link android.view.View} changes etc. Parties interested in handling accessibility
50  * events implement and register an accessibility service which extends
51  * {@link android.accessibilityservice.AccessibilityService}.
52  * <p>
53  * To obtain a handle to the accessibility manager do the following:
54  * </p>
55  * <p>
56  * <code>
57  * <pre>AccessibilityManager accessibilityManager =
58  *        (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre>
59  * </code>
60  * </p>
61  *
62  * @see AccessibilityEvent
63  * @see AccessibilityNodeInfo
64  * @see android.accessibilityservice.AccessibilityService
65  * @see Context#getSystemService
66  * @see Context#ACCESSIBILITY_SERVICE
67  */
68 public final class AccessibilityManager {
69     private static final boolean DEBUG = false;
70 
71     private static final String LOG_TAG = "AccessibilityManager";
72 
73     /** @hide */
74     public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
75 
76     /** @hide */
77     public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
78 
79     /** @hide */
80     public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
81 
82     /** @hide */
83     public static final int DALTONIZER_DISABLED = -1;
84 
85     /** @hide */
86     public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
87 
88     /** @hide */
89     public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
90 
91     /** @hide */
92     public static final int AUTOCLICK_DELAY_DEFAULT = 600;
93 
94     static final Object sInstanceSync = new Object();
95 
96     private static AccessibilityManager sInstance;
97 
98     private final Object mLock = new Object();
99 
100     private IAccessibilityManager mService;
101 
102     final int mUserId;
103 
104     final Handler mHandler;
105 
106     boolean mIsEnabled;
107 
108     boolean mIsTouchExplorationEnabled;
109 
110     boolean mIsHighTextContrastEnabled;
111 
112     private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
113             mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>();
114 
115     private final CopyOnWriteArrayList<TouchExplorationStateChangeListener>
116             mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<>();
117 
118     private final CopyOnWriteArrayList<HighTextContrastChangeListener>
119             mHighTextContrastStateChangeListeners = new CopyOnWriteArrayList<>();
120 
121     /**
122      * Listener for the system accessibility state. To listen for changes to the
123      * accessibility state on the device, implement this interface and register
124      * it with the system by calling {@link #addAccessibilityStateChangeListener}.
125      */
126     public interface AccessibilityStateChangeListener {
127 
128         /**
129          * Called when the accessibility enabled state changes.
130          *
131          * @param enabled Whether accessibility is enabled.
132          */
onAccessibilityStateChanged(boolean enabled)133         public void onAccessibilityStateChanged(boolean enabled);
134     }
135 
136     /**
137      * Listener for the system touch exploration state. To listen for changes to
138      * the touch exploration state on the device, implement this interface and
139      * register it with the system by calling
140      * {@link #addTouchExplorationStateChangeListener}.
141      */
142     public interface TouchExplorationStateChangeListener {
143 
144         /**
145          * Called when the touch exploration enabled state changes.
146          *
147          * @param enabled Whether touch exploration is enabled.
148          */
onTouchExplorationStateChanged(boolean enabled)149         public void onTouchExplorationStateChanged(boolean enabled);
150     }
151 
152     /**
153      * Listener for the system high text contrast state. To listen for changes to
154      * the high text contrast state on the device, implement this interface and
155      * register it with the system by calling
156      * {@link #addHighTextContrastStateChangeListener}.
157      *
158      * @hide
159      */
160     public interface HighTextContrastChangeListener {
161 
162         /**
163          * Called when the high text contrast enabled state changes.
164          *
165          * @param enabled Whether high text contrast is enabled.
166          */
onHighTextContrastStateChanged(boolean enabled)167         public void onHighTextContrastStateChanged(boolean enabled);
168     }
169 
170     private final IAccessibilityManagerClient.Stub mClient =
171             new IAccessibilityManagerClient.Stub() {
172         public void setState(int state) {
173             // We do not want to change this immediately as the applicatoin may
174             // have already checked that accessibility is on and fired an event,
175             // that is now propagating up the view tree, Hence, if accessibility
176             // is now off an exception will be thrown. We want to have the exception
177             // enforcement to guard against apps that fire unnecessary accessibility
178             // events when accessibility is off.
179             mHandler.obtainMessage(MyHandler.MSG_SET_STATE, state, 0).sendToTarget();
180         }
181     };
182 
183     /**
184      * Get an AccessibilityManager instance (create one if necessary).
185      *
186      * @param context Context in which this manager operates.
187      *
188      * @hide
189      */
getInstance(Context context)190     public static AccessibilityManager getInstance(Context context) {
191         synchronized (sInstanceSync) {
192             if (sInstance == null) {
193                 final int userId;
194                 if (Binder.getCallingUid() == Process.SYSTEM_UID
195                         || context.checkCallingOrSelfPermission(
196                                 Manifest.permission.INTERACT_ACROSS_USERS)
197                                         == PackageManager.PERMISSION_GRANTED
198                         || context.checkCallingOrSelfPermission(
199                                 Manifest.permission.INTERACT_ACROSS_USERS_FULL)
200                                         == PackageManager.PERMISSION_GRANTED) {
201                     userId = UserHandle.USER_CURRENT;
202                 } else {
203                     userId = UserHandle.myUserId();
204                 }
205                 sInstance = new AccessibilityManager(context, null, userId);
206             }
207         }
208         return sInstance;
209     }
210 
211     /**
212      * Create an instance.
213      *
214      * @param context A {@link Context}.
215      * @param service An interface to the backing service.
216      * @param userId User id under which to run.
217      *
218      * @hide
219      */
AccessibilityManager(Context context, IAccessibilityManager service, int userId)220     public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
221         mHandler = new MyHandler(context.getMainLooper());
222         mUserId = userId;
223         synchronized (mLock) {
224             tryConnectToServiceLocked(service);
225         }
226     }
227 
228     /**
229      * @hide
230      */
getClient()231     public IAccessibilityManagerClient getClient() {
232         return mClient;
233     }
234 
235     /**
236      * Returns if the accessibility in the system is enabled.
237      *
238      * @return True if accessibility is enabled, false otherwise.
239      */
isEnabled()240     public boolean isEnabled() {
241         synchronized (mLock) {
242             IAccessibilityManager service = getServiceLocked();
243             if (service == null) {
244                 return false;
245             }
246             return mIsEnabled;
247         }
248     }
249 
250     /**
251      * Returns if the touch exploration in the system is enabled.
252      *
253      * @return True if touch exploration is enabled, false otherwise.
254      */
isTouchExplorationEnabled()255     public boolean isTouchExplorationEnabled() {
256         synchronized (mLock) {
257             IAccessibilityManager service = getServiceLocked();
258             if (service == null) {
259                 return false;
260             }
261             return mIsTouchExplorationEnabled;
262         }
263     }
264 
265     /**
266      * Returns if the high text contrast in the system is enabled.
267      * <p>
268      * <strong>Note:</strong> You need to query this only if you application is
269      * doing its own rendering and does not rely on the platform rendering pipeline.
270      * </p>
271      *
272      * @return True if high text contrast is enabled, false otherwise.
273      *
274      * @hide
275      */
isHighTextContrastEnabled()276     public boolean isHighTextContrastEnabled() {
277         synchronized (mLock) {
278             IAccessibilityManager service = getServiceLocked();
279             if (service == null) {
280                 return false;
281             }
282             return mIsHighTextContrastEnabled;
283         }
284     }
285 
286     /**
287      * Sends an {@link AccessibilityEvent}.
288      *
289      * @param event The event to send.
290      *
291      * @throws IllegalStateException if accessibility is not enabled.
292      *
293      * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
294      * events is through calling
295      * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
296      * instead of this method to allow predecessors to augment/filter events sent by
297      * their descendants.
298      */
sendAccessibilityEvent(AccessibilityEvent event)299     public void sendAccessibilityEvent(AccessibilityEvent event) {
300         final IAccessibilityManager service;
301         final int userId;
302         synchronized (mLock) {
303             service = getServiceLocked();
304             if (service == null) {
305                 return;
306             }
307             if (!mIsEnabled) {
308                 throw new IllegalStateException("Accessibility off. Did you forget to check that?");
309             }
310             userId = mUserId;
311         }
312         boolean doRecycle = false;
313         try {
314             event.setEventTime(SystemClock.uptimeMillis());
315             // it is possible that this manager is in the same process as the service but
316             // client using it is called through Binder from another process. Example: MMS
317             // app adds a SMS notification and the NotificationManagerService calls this method
318             long identityToken = Binder.clearCallingIdentity();
319             doRecycle = service.sendAccessibilityEvent(event, userId);
320             Binder.restoreCallingIdentity(identityToken);
321             if (DEBUG) {
322                 Log.i(LOG_TAG, event + " sent");
323             }
324         } catch (RemoteException re) {
325             Log.e(LOG_TAG, "Error during sending " + event + " ", re);
326         } finally {
327             if (doRecycle) {
328                 event.recycle();
329             }
330         }
331     }
332 
333     /**
334      * Requests feedback interruption from all accessibility services.
335      */
interrupt()336     public void interrupt() {
337         final IAccessibilityManager service;
338         final int userId;
339         synchronized (mLock) {
340             service = getServiceLocked();
341             if (service == null) {
342                 return;
343             }
344             if (!mIsEnabled) {
345                 throw new IllegalStateException("Accessibility off. Did you forget to check that?");
346             }
347             userId = mUserId;
348         }
349         try {
350             service.interrupt(userId);
351             if (DEBUG) {
352                 Log.i(LOG_TAG, "Requested interrupt from all services");
353             }
354         } catch (RemoteException re) {
355             Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
356         }
357     }
358 
359     /**
360      * Returns the {@link ServiceInfo}s of the installed accessibility services.
361      *
362      * @return An unmodifiable list with {@link ServiceInfo}s.
363      *
364      * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
365      */
366     @Deprecated
getAccessibilityServiceList()367     public List<ServiceInfo> getAccessibilityServiceList() {
368         List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
369         List<ServiceInfo> services = new ArrayList<>();
370         final int infoCount = infos.size();
371         for (int i = 0; i < infoCount; i++) {
372             AccessibilityServiceInfo info = infos.get(i);
373             services.add(info.getResolveInfo().serviceInfo);
374         }
375         return Collections.unmodifiableList(services);
376     }
377 
378     /**
379      * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
380      *
381      * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
382      */
getInstalledAccessibilityServiceList()383     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
384         final IAccessibilityManager service;
385         final int userId;
386         synchronized (mLock) {
387             service = getServiceLocked();
388             if (service == null) {
389                 return Collections.emptyList();
390             }
391             userId = mUserId;
392         }
393 
394         List<AccessibilityServiceInfo> services = null;
395         try {
396             services = service.getInstalledAccessibilityServiceList(userId);
397             if (DEBUG) {
398                 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
399             }
400         } catch (RemoteException re) {
401             Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
402         }
403         if (services != null) {
404             return Collections.unmodifiableList(services);
405         } else {
406             return Collections.emptyList();
407         }
408     }
409 
410     /**
411      * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
412      * for a given feedback type.
413      *
414      * @param feedbackTypeFlags The feedback type flags.
415      * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
416      *
417      * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
418      * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
419      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
420      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
421      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
422      * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
423      */
getEnabledAccessibilityServiceList( int feedbackTypeFlags)424     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
425             int feedbackTypeFlags) {
426         final IAccessibilityManager service;
427         final int userId;
428         synchronized (mLock) {
429             service = getServiceLocked();
430             if (service == null) {
431                 return Collections.emptyList();
432             }
433             userId = mUserId;
434         }
435 
436         List<AccessibilityServiceInfo> services = null;
437         try {
438             services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
439             if (DEBUG) {
440                 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
441             }
442         } catch (RemoteException re) {
443             Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
444         }
445         if (services != null) {
446             return Collections.unmodifiableList(services);
447         } else {
448             return Collections.emptyList();
449         }
450     }
451 
452     /**
453      * Registers an {@link AccessibilityStateChangeListener} for changes in
454      * the global accessibility state of the system.
455      *
456      * @param listener The listener.
457      * @return True if successfully registered.
458      */
addAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)459     public boolean addAccessibilityStateChangeListener(
460             @NonNull AccessibilityStateChangeListener listener) {
461         // Final CopyOnWriteArrayList - no lock needed.
462         return mAccessibilityStateChangeListeners.add(listener);
463     }
464 
465     /**
466      * Unregisters an {@link AccessibilityStateChangeListener}.
467      *
468      * @param listener The listener.
469      * @return True if successfully unregistered.
470      */
removeAccessibilityStateChangeListener( @onNull AccessibilityStateChangeListener listener)471     public boolean removeAccessibilityStateChangeListener(
472             @NonNull AccessibilityStateChangeListener listener) {
473         // Final CopyOnWriteArrayList - no lock needed.
474         return mAccessibilityStateChangeListeners.remove(listener);
475     }
476 
477     /**
478      * Registers a {@link TouchExplorationStateChangeListener} for changes in
479      * the global touch exploration state of the system.
480      *
481      * @param listener The listener.
482      * @return True if successfully registered.
483      */
addTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)484     public boolean addTouchExplorationStateChangeListener(
485             @NonNull TouchExplorationStateChangeListener listener) {
486         // Final CopyOnWriteArrayList - no lock needed.
487         return mTouchExplorationStateChangeListeners.add(listener);
488     }
489 
490     /**
491      * Unregisters a {@link TouchExplorationStateChangeListener}.
492      *
493      * @param listener The listener.
494      * @return True if successfully unregistered.
495      */
removeTouchExplorationStateChangeListener( @onNull TouchExplorationStateChangeListener listener)496     public boolean removeTouchExplorationStateChangeListener(
497             @NonNull TouchExplorationStateChangeListener listener) {
498         // Final CopyOnWriteArrayList - no lock needed.
499         return mTouchExplorationStateChangeListeners.remove(listener);
500     }
501 
502     /**
503      * Registers a {@link HighTextContrastChangeListener} for changes in
504      * the global high text contrast state of the system.
505      *
506      * @param listener The listener.
507      * @return True if successfully registered.
508      *
509      * @hide
510      */
addHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener)511     public boolean addHighTextContrastStateChangeListener(
512             @NonNull HighTextContrastChangeListener listener) {
513         // Final CopyOnWriteArrayList - no lock needed.
514         return mHighTextContrastStateChangeListeners.add(listener);
515     }
516 
517     /**
518      * Unregisters a {@link HighTextContrastChangeListener}.
519      *
520      * @param listener The listener.
521      * @return True if successfully unregistered.
522      *
523      * @hide
524      */
removeHighTextContrastStateChangeListener( @onNull HighTextContrastChangeListener listener)525     public boolean removeHighTextContrastStateChangeListener(
526             @NonNull HighTextContrastChangeListener listener) {
527         // Final CopyOnWriteArrayList - no lock needed.
528         return mHighTextContrastStateChangeListeners.remove(listener);
529     }
530 
531     /**
532      * Sets the current state and notifies listeners, if necessary.
533      *
534      * @param stateFlags The state flags.
535      */
setStateLocked(int stateFlags)536     private void setStateLocked(int stateFlags) {
537         final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
538         final boolean touchExplorationEnabled =
539                 (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
540         final boolean highTextContrastEnabled =
541                 (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
542 
543         final boolean wasEnabled = mIsEnabled;
544         final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
545         final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
546 
547         // Ensure listeners get current state from isZzzEnabled() calls.
548         mIsEnabled = enabled;
549         mIsTouchExplorationEnabled = touchExplorationEnabled;
550         mIsHighTextContrastEnabled = highTextContrastEnabled;
551 
552         if (wasEnabled != enabled) {
553             mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
554         }
555 
556         if (wasTouchExplorationEnabled != touchExplorationEnabled) {
557             mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
558         }
559 
560         if (wasHighTextContrastEnabled != highTextContrastEnabled) {
561             mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
562         }
563     }
564 
565     /**
566      * Adds an accessibility interaction connection interface for a given window.
567      * @param windowToken The window token to which a connection is added.
568      * @param connection The connection.
569      *
570      * @hide
571      */
addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection)572     public int addAccessibilityInteractionConnection(IWindow windowToken,
573             IAccessibilityInteractionConnection connection) {
574         final IAccessibilityManager service;
575         final int userId;
576         synchronized (mLock) {
577             service = getServiceLocked();
578             if (service == null) {
579                 return View.NO_ID;
580             }
581             userId = mUserId;
582         }
583         try {
584             return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
585         } catch (RemoteException re) {
586             Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
587         }
588         return View.NO_ID;
589     }
590 
591     /**
592      * Removed an accessibility interaction connection interface for a given window.
593      * @param windowToken The window token to which a connection is removed.
594      *
595      * @hide
596      */
removeAccessibilityInteractionConnection(IWindow windowToken)597     public void removeAccessibilityInteractionConnection(IWindow windowToken) {
598         final IAccessibilityManager service;
599         synchronized (mLock) {
600             service = getServiceLocked();
601             if (service == null) {
602                 return;
603             }
604         }
605         try {
606             service.removeAccessibilityInteractionConnection(windowToken);
607         } catch (RemoteException re) {
608             Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
609         }
610     }
611 
getServiceLocked()612     private  IAccessibilityManager getServiceLocked() {
613         if (mService == null) {
614             tryConnectToServiceLocked(null);
615         }
616         return mService;
617     }
618 
tryConnectToServiceLocked(IAccessibilityManager service)619     private void tryConnectToServiceLocked(IAccessibilityManager service) {
620         if (service == null) {
621             IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
622             if (iBinder == null) {
623                 return;
624             }
625             service = IAccessibilityManager.Stub.asInterface(iBinder);
626         }
627 
628         try {
629             final int stateFlags = service.addClient(mClient, mUserId);
630             setStateLocked(stateFlags);
631             mService = service;
632         } catch (RemoteException re) {
633             Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
634         }
635     }
636 
637     /**
638      * Notifies the registered {@link AccessibilityStateChangeListener}s.
639      */
handleNotifyAccessibilityStateChanged()640     private void handleNotifyAccessibilityStateChanged() {
641         final boolean isEnabled;
642         synchronized (mLock) {
643             isEnabled = mIsEnabled;
644         }
645         // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
646         for (AccessibilityStateChangeListener listener :mAccessibilityStateChangeListeners) {
647             listener.onAccessibilityStateChanged(isEnabled);
648         }
649     }
650 
651     /**
652      * Notifies the registered {@link TouchExplorationStateChangeListener}s.
653      */
handleNotifyTouchExplorationStateChanged()654     private void handleNotifyTouchExplorationStateChanged() {
655         final boolean isTouchExplorationEnabled;
656         synchronized (mLock) {
657             isTouchExplorationEnabled = mIsTouchExplorationEnabled;
658         }
659         // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
660         for (TouchExplorationStateChangeListener listener :mTouchExplorationStateChangeListeners) {
661             listener.onTouchExplorationStateChanged(isTouchExplorationEnabled);
662         }
663     }
664 
665     /**
666      * Notifies the registered {@link HighTextContrastChangeListener}s.
667      */
handleNotifyHighTextContrastStateChanged()668     private void handleNotifyHighTextContrastStateChanged() {
669         final boolean isHighTextContrastEnabled;
670         synchronized (mLock) {
671             isHighTextContrastEnabled = mIsHighTextContrastEnabled;
672         }
673         // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
674         for (HighTextContrastChangeListener listener : mHighTextContrastStateChangeListeners) {
675             listener.onHighTextContrastStateChanged(isHighTextContrastEnabled);
676         }
677     }
678 
679     private final class MyHandler extends Handler {
680         public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
681         public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
682         public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3;
683         public static final int MSG_SET_STATE = 4;
684 
MyHandler(Looper looper)685         public MyHandler(Looper looper) {
686             super(looper, null, false);
687         }
688 
689         @Override
handleMessage(Message message)690         public void handleMessage(Message message) {
691             switch (message.what) {
692                 case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
693                     handleNotifyAccessibilityStateChanged();
694                 } break;
695 
696                 case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: {
697                     handleNotifyTouchExplorationStateChanged();
698                 } break;
699 
700                 case MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED: {
701                     handleNotifyHighTextContrastStateChanged();
702                 } break;
703 
704                 case MSG_SET_STATE: {
705                     // See comment at mClient
706                     final int state = message.arg1;
707                     synchronized (mLock) {
708                         setStateLocked(state);
709                     }
710                 } break;
711             }
712         }
713     }
714 }
715