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