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