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 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          */
    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          */
    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          */
    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          */
    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      */
    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      */
    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      */
    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      */
    320     public IAccessibilityManagerClient getClient() {
    321         return mClient;
    322     }
    323 
    324     /**
    325      * @hide
    326      */
    327     @VisibleForTesting
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    962     private IAccessibilityManager getServiceLocked() {
    963         if (mService == null) {
    964             tryConnectToServiceLocked(null);
    965         }
    966         return mService;
    967     }
    968 
    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      */
    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      */
   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      */
   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
   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
   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