Home | History | Annotate | Download | only in accessibility
      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          */
    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          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    613     private  IAccessibilityManager getServiceLocked() {
    614         if (mService == null) {
    615             tryConnectToServiceLocked();
    616         }
    617         return mService;
    618     }
    619 
    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      */
    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      */
    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      */
    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 
    683         public MyHandler(Looper looper) {
    684             super(looper, null, false);
    685         }
    686 
    687         @Override
    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