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