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.accessibilityservice.AccessibilityServiceInfo;
     20 import android.content.Context;
     21 import android.content.pm.ServiceInfo;
     22 import android.os.Binder;
     23 import android.os.Handler;
     24 import android.os.IBinder;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.RemoteException;
     28 import android.os.ServiceManager;
     29 import android.os.SystemClock;
     30 import android.os.UserHandle;
     31 import android.util.Log;
     32 import android.view.IWindow;
     33 import android.view.View;
     34 
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.List;
     38 import java.util.concurrent.CopyOnWriteArrayList;
     39 
     40 /**
     41  * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
     42  * and provides facilities for querying the accessibility state of the system.
     43  * Accessibility events are generated when something notable happens in the user interface,
     44  * for example an {@link android.app.Activity} starts, the focus or selection of a
     45  * {@link android.view.View} changes etc. Parties interested in handling accessibility
     46  * events implement and register an accessibility service which extends
     47  * {@link android.accessibilityservice.AccessibilityService}.
     48  * <p>
     49  * To obtain a handle to the accessibility manager do the following:
     50  * </p>
     51  * <p>
     52  * <code>
     53  * <pre>AccessibilityManager accessibilityManager =
     54  *        (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);</pre>
     55  * </code>
     56  * </p>
     57  *
     58  * @see AccessibilityEvent
     59  * @see AccessibilityNodeInfo
     60  * @see android.accessibilityservice.AccessibilityService
     61  * @see Context#getSystemService
     62  * @see Context#ACCESSIBILITY_SERVICE
     63  */
     64 public final class AccessibilityManager {
     65     private static final boolean DEBUG = false;
     66 
     67     private static final String LOG_TAG = "AccessibilityManager";
     68 
     69     /** @hide */
     70     public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
     71 
     72     /** @hide */
     73     public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
     74 
     75     static final Object sInstanceSync = new Object();
     76 
     77     private static AccessibilityManager sInstance;
     78 
     79     private static final int DO_SET_STATE = 10;
     80 
     81     final IAccessibilityManager mService;
     82 
     83     final int mUserId;
     84 
     85     final Handler mHandler;
     86 
     87     boolean mIsEnabled;
     88 
     89     boolean mIsTouchExplorationEnabled;
     90 
     91     final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners =
     92         new CopyOnWriteArrayList<AccessibilityStateChangeListener>();
     93 
     94     /**
     95      * Listener for the system accessibility state. To listen for changes to the accessibility
     96      * state on the device, implement this interface and register it with the system by
     97      * calling {@link AccessibilityManager#addAccessibilityStateChangeListener
     98      * addAccessibilityStateChangeListener()}.
     99      */
    100     public interface AccessibilityStateChangeListener {
    101 
    102         /**
    103          * Called back on change in the accessibility state.
    104          *
    105          * @param enabled Whether accessibility is enabled.
    106          */
    107         public void onAccessibilityStateChanged(boolean enabled);
    108     }
    109 
    110     final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
    111         public void setState(int state) {
    112             mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget();
    113         }
    114     };
    115 
    116     class MyHandler extends Handler {
    117 
    118         MyHandler(Looper mainLooper) {
    119             super(mainLooper);
    120         }
    121 
    122         @Override
    123         public void handleMessage(Message message) {
    124             switch (message.what) {
    125                 case DO_SET_STATE :
    126                     setState(message.arg1);
    127                     return;
    128                 default :
    129                     Log.w(LOG_TAG, "Unknown message type: " + message.what);
    130             }
    131         }
    132     }
    133 
    134     /**
    135      * Creates the singleton AccessibilityManager to be shared across users. This
    136      * has to be called before the local AccessibilityManager is created to ensure
    137      * it registers itself in the system correctly.
    138      * <p>
    139      * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or
    140      *       INTERACT_ACROSS_USERS permission.
    141      * </p>
    142      * @param context Context in which this manager operates.
    143      * @throws IllegalStateException if not called before the local
    144      *     AccessibilityManager is instantiated.
    145      *
    146      * @hide
    147      */
    148     public static void createAsSharedAcrossUsers(Context context) {
    149         synchronized (sInstanceSync) {
    150             if (sInstance != null) {
    151                 throw new IllegalStateException("AccessibilityManager already created.");
    152             }
    153             createSingletonInstance(context, UserHandle.USER_CURRENT);
    154         }
    155     }
    156 
    157     /**
    158      * Get an AccessibilityManager instance (create one if necessary).
    159      *
    160      * @param context Context in which this manager operates.
    161      *
    162      * @hide
    163      */
    164     public static AccessibilityManager getInstance(Context context) {
    165         synchronized (sInstanceSync) {
    166             if (sInstance == null) {
    167                 createSingletonInstance(context, UserHandle.myUserId());
    168             }
    169         }
    170         return sInstance;
    171     }
    172 
    173     /**
    174      * Creates the singleton instance.
    175      *
    176      * @param context Context in which this manager operates.
    177      * @param userId The user id under which to operate.
    178      */
    179     private static void createSingletonInstance(Context context, int userId) {
    180         IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
    181         IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
    182         sInstance = new AccessibilityManager(context, service, userId);
    183     }
    184 
    185     /**
    186      * Create an instance.
    187      *
    188      * @param context A {@link Context}.
    189      * @param service An interface to the backing service.
    190      * @param userId User id under which to run.
    191      *
    192      * @hide
    193      */
    194     public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
    195         mHandler = new MyHandler(context.getMainLooper());
    196         mService = service;
    197         mUserId = userId;
    198 
    199         try {
    200             final int stateFlags = mService.addClient(mClient, userId);
    201             setState(stateFlags);
    202         } catch (RemoteException re) {
    203             Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
    204         }
    205     }
    206 
    207     /**
    208      * Returns if the accessibility in the system is enabled.
    209      *
    210      * @return True if accessibility is enabled, false otherwise.
    211      */
    212     public boolean isEnabled() {
    213         synchronized (mHandler) {
    214             return mIsEnabled;
    215         }
    216     }
    217 
    218     /**
    219      * Returns if the touch exploration in the system is enabled.
    220      *
    221      * @return True if touch exploration is enabled, false otherwise.
    222      */
    223     public boolean isTouchExplorationEnabled() {
    224         synchronized (mHandler) {
    225             return mIsTouchExplorationEnabled;
    226         }
    227     }
    228 
    229     /**
    230      * Returns the client interface this instance registers in
    231      * the centralized accessibility manager service.
    232      *
    233      * @return The client.
    234      *
    235      * @hide
    236      */
    237     public IAccessibilityManagerClient getClient() {
    238        return (IAccessibilityManagerClient) mClient.asBinder();
    239     }
    240 
    241     /**
    242      * Sends an {@link AccessibilityEvent}.
    243      *
    244      * @param event The event to send.
    245      *
    246      * @throws IllegalStateException if accessibility is not enabled.
    247      *
    248      * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
    249      * events is through calling
    250      * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
    251      * instead of this method to allow predecessors to augment/filter events sent by
    252      * their descendants.
    253      */
    254     public void sendAccessibilityEvent(AccessibilityEvent event) {
    255         if (!mIsEnabled) {
    256             throw new IllegalStateException("Accessibility off. Did you forget to check that?");
    257         }
    258         boolean doRecycle = false;
    259         try {
    260             event.setEventTime(SystemClock.uptimeMillis());
    261             // it is possible that this manager is in the same process as the service but
    262             // client using it is called through Binder from another process. Example: MMS
    263             // app adds a SMS notification and the NotificationManagerService calls this method
    264             long identityToken = Binder.clearCallingIdentity();
    265             doRecycle = mService.sendAccessibilityEvent(event, mUserId);
    266             Binder.restoreCallingIdentity(identityToken);
    267             if (DEBUG) {
    268                 Log.i(LOG_TAG, event + " sent");
    269             }
    270         } catch (RemoteException re) {
    271             Log.e(LOG_TAG, "Error during sending " + event + " ", re);
    272         } finally {
    273             if (doRecycle) {
    274                 event.recycle();
    275             }
    276         }
    277     }
    278 
    279     /**
    280      * Requests feedback interruption from all accessibility services.
    281      */
    282     public void interrupt() {
    283         if (!mIsEnabled) {
    284             throw new IllegalStateException("Accessibility off. Did you forget to check that?");
    285         }
    286         try {
    287             mService.interrupt(mUserId);
    288             if (DEBUG) {
    289                 Log.i(LOG_TAG, "Requested interrupt from all services");
    290             }
    291         } catch (RemoteException re) {
    292             Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
    293         }
    294     }
    295 
    296     /**
    297      * Returns the {@link ServiceInfo}s of the installed accessibility services.
    298      *
    299      * @return An unmodifiable list with {@link ServiceInfo}s.
    300      *
    301      * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
    302      */
    303     @Deprecated
    304     public List<ServiceInfo> getAccessibilityServiceList() {
    305         List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
    306         List<ServiceInfo> services = new ArrayList<ServiceInfo>();
    307         final int infoCount = infos.size();
    308         for (int i = 0; i < infoCount; i++) {
    309             AccessibilityServiceInfo info = infos.get(i);
    310             services.add(info.getResolveInfo().serviceInfo);
    311         }
    312         return Collections.unmodifiableList(services);
    313     }
    314 
    315     /**
    316      * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
    317      *
    318      * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
    319      */
    320     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
    321         List<AccessibilityServiceInfo> services = null;
    322         try {
    323             services = mService.getInstalledAccessibilityServiceList(mUserId);
    324             if (DEBUG) {
    325                 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
    326             }
    327         } catch (RemoteException re) {
    328             Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
    329         }
    330         return Collections.unmodifiableList(services);
    331     }
    332 
    333     /**
    334      * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
    335      * for a given feedback type.
    336      *
    337      * @param feedbackTypeFlags The feedback type flags.
    338      * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
    339      *
    340      * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
    341      * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
    342      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
    343      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
    344      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
    345      */
    346     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
    347             int feedbackTypeFlags) {
    348         List<AccessibilityServiceInfo> services = null;
    349         try {
    350             services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId);
    351             if (DEBUG) {
    352                 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
    353             }
    354         } catch (RemoteException re) {
    355             Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
    356         }
    357         return Collections.unmodifiableList(services);
    358     }
    359 
    360     /**
    361      * Registers an {@link AccessibilityStateChangeListener} for changes in
    362      * the global accessibility state of the system.
    363      *
    364      * @param listener The listener.
    365      * @return True if successfully registered.
    366      */
    367     public boolean addAccessibilityStateChangeListener(
    368             AccessibilityStateChangeListener listener) {
    369         return mAccessibilityStateChangeListeners.add(listener);
    370     }
    371 
    372     /**
    373      * Unregisters an {@link AccessibilityStateChangeListener}.
    374      *
    375      * @param listener The listener.
    376      * @return True if successfully unregistered.
    377      */
    378     public boolean removeAccessibilityStateChangeListener(
    379             AccessibilityStateChangeListener listener) {
    380         return mAccessibilityStateChangeListeners.remove(listener);
    381     }
    382 
    383     /**
    384      * Sets the current state.
    385      *
    386      * @param stateFlags The state flags.
    387      */
    388     private void setState(int stateFlags) {
    389         final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
    390         setAccessibilityState(accessibilityEnabled);
    391         mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
    392     }
    393 
    394     /**
    395      * Sets the enabled state.
    396      *
    397      * @param isEnabled The accessibility state.
    398      */
    399     private void setAccessibilityState(boolean isEnabled) {
    400         synchronized (mHandler) {
    401             if (isEnabled != mIsEnabled) {
    402                 mIsEnabled = isEnabled;
    403                 notifyAccessibilityStateChanged();
    404             }
    405         }
    406     }
    407 
    408     /**
    409      * Notifies the registered {@link AccessibilityStateChangeListener}s.
    410      */
    411     private void notifyAccessibilityStateChanged() {
    412         final int listenerCount = mAccessibilityStateChangeListeners.size();
    413         for (int i = 0; i < listenerCount; i++) {
    414             mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
    415         }
    416     }
    417 
    418     /**
    419      * Adds an accessibility interaction connection interface for a given window.
    420      * @param windowToken The window token to which a connection is added.
    421      * @param connection The connection.
    422      *
    423      * @hide
    424      */
    425     public int addAccessibilityInteractionConnection(IWindow windowToken,
    426             IAccessibilityInteractionConnection connection) {
    427         try {
    428             return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId);
    429         } catch (RemoteException re) {
    430             Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
    431         }
    432         return View.NO_ID;
    433     }
    434 
    435     /**
    436      * Removed an accessibility interaction connection interface for a given window.
    437      * @param windowToken The window token to which a connection is removed.
    438      *
    439      * @hide
    440      */
    441     public void removeAccessibilityInteractionConnection(IWindow windowToken) {
    442         try {
    443             mService.removeAccessibilityInteractionConnection(windowToken);
    444         } catch (RemoteException re) {
    445             Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
    446         }
    447     }
    448 }
    449