AccessibilityManager.java revision 4dfecf55c1afcc7ffe0cef931df67c4934a13e34
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        return sInstance;
137    }
138
139    /**
140     * Create an instance.
141     *
142     * @param context A {@link Context}.
143     * @param service An interface to the backing service.
144     *
145     * @hide
146     */
147    public AccessibilityManager(Context context, IAccessibilityManager service) {
148        mHandler = new MyHandler(context.getMainLooper());
149        mService = service;
150
151        try {
152            final boolean isEnabled = mService.addClient(mClient);
153            setAccessibilityState(isEnabled);
154        } catch (RemoteException re) {
155            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
156        }
157    }
158
159    /**
160     * Returns if the accessibility in the system is enabled.
161     *
162     * @return True if accessibility is enabled, false otherwise.
163     */
164    public boolean isEnabled() {
165        synchronized (mHandler) {
166            return mIsEnabled;
167        }
168    }
169
170    /**
171     * Returns the client interface this instance registers in
172     * the centralized accessibility manager service.
173     *
174     * @return The client.
175     *
176     * @hide
177     */
178    public IAccessibilityManagerClient getClient() {
179       return (IAccessibilityManagerClient) mClient.asBinder();
180    }
181
182    /**
183     * Sends an {@link AccessibilityEvent}.
184     *
185     * @param event The event to send.
186     *
187     * @throws IllegalStateException if accessibility is not enabled.
188     */
189    public void sendAccessibilityEvent(AccessibilityEvent event) {
190        if (!mIsEnabled) {
191            throw new IllegalStateException("Accessibility off. Did you forget to check that?");
192        }
193        boolean doRecycle = false;
194        try {
195            event.setEventTime(SystemClock.uptimeMillis());
196            // it is possible that this manager is in the same process as the service but
197            // client using it is called through Binder from another process. Example: MMS
198            // app adds a SMS notification and the NotificationManagerService calls this method
199            long identityToken = Binder.clearCallingIdentity();
200            doRecycle = mService.sendAccessibilityEvent(event);
201            Binder.restoreCallingIdentity(identityToken);
202            if (DEBUG) {
203                Log.i(LOG_TAG, event + " sent");
204            }
205        } catch (RemoteException re) {
206            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
207        } finally {
208            if (doRecycle) {
209                event.recycle();
210            }
211        }
212    }
213
214    /**
215     * Requests feedback interruption from all accessibility services.
216     */
217    public void interrupt() {
218        if (!mIsEnabled) {
219            throw new IllegalStateException("Accessibility off. Did you forget to check that?");
220        }
221        try {
222            mService.interrupt();
223            if (DEBUG) {
224                Log.i(LOG_TAG, "Requested interrupt from all services");
225            }
226        } catch (RemoteException re) {
227            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
228        }
229    }
230
231    /**
232     * Returns the {@link ServiceInfo}s of the installed accessibility services.
233     *
234     * @return An unmodifiable list with {@link ServiceInfo}s.
235     *
236     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
237     */
238    @Deprecated
239    public List<ServiceInfo> getAccessibilityServiceList() {
240        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
241        List<ServiceInfo> services = new ArrayList<ServiceInfo>();
242        final int infoCount = infos.size();
243        for (int i = 0; i < infoCount; i++) {
244            AccessibilityServiceInfo info = infos.get(i);
245            services.add(info.getResolveInfo().serviceInfo);
246        }
247        return Collections.unmodifiableList(services);
248    }
249
250    /**
251     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
252     *
253     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
254     */
255    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
256        List<AccessibilityServiceInfo> services = null;
257        try {
258            services = mService.getInstalledAccessibilityServiceList();
259            if (DEBUG) {
260                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
261            }
262        } catch (RemoteException re) {
263            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
264        }
265        return Collections.unmodifiableList(services);
266    }
267
268    /**
269     * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
270     * for a given feedback type.
271     *
272     * @param feedbackTypeFlags The feedback type flags.
273     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
274     *
275     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
276     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
277     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
278     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
279     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
280     */
281    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
282            int feedbackTypeFlags) {
283        List<AccessibilityServiceInfo> services = null;
284        try {
285            services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags);
286            if (DEBUG) {
287                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
288            }
289        } catch (RemoteException re) {
290            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
291        }
292        return Collections.unmodifiableList(services);
293    }
294
295    /**
296     * Registers an {@link AccessibilityStateChangeListener} for changes in
297     * the global accessibility state of the system.
298     *
299     * @param listener The listener.
300     * @return True if successfully registered.
301     */
302    public boolean addAccessibilityStateChangeListener(
303            AccessibilityStateChangeListener listener) {
304        return mAccessibilityStateChangeListeners.add(listener);
305    }
306
307    /**
308     * Unregisters an {@link AccessibilityStateChangeListener}.
309     *
310     * @param listener The listener.
311     * @return True if successfully unregistered.
312     */
313    public boolean removeAccessibilityStateChangeListener(
314            AccessibilityStateChangeListener listener) {
315        return mAccessibilityStateChangeListeners.remove(listener);
316    }
317
318    /**
319     * Sets the enabled state.
320     *
321     * @param isEnabled The accessibility state.
322     */
323    private void setAccessibilityState(boolean isEnabled) {
324        synchronized (mHandler) {
325            if (isEnabled != mIsEnabled) {
326                mIsEnabled = isEnabled;
327                notifyAccessibilityStateChanged();
328            }
329        }
330    }
331
332    /**
333     * Notifies the registered {@link AccessibilityStateChangeListener}s.
334     */
335    private void notifyAccessibilityStateChanged() {
336        final int listenerCount = mAccessibilityStateChangeListeners.size();
337        for (int i = 0; i < listenerCount; i++) {
338            mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
339        }
340    }
341
342    /**
343     * Adds an accessibility interaction connection interface for a given window.
344     * @param windowToken The window token to which a connection is added.
345     * @param connection The connection.
346     *
347     * @hide
348     */
349    public int addAccessibilityInteractionConnection(IWindow windowToken,
350            IAccessibilityInteractionConnection connection) {
351        try {
352            return mService.addAccessibilityInteractionConnection(windowToken, connection);
353        } catch (RemoteException re) {
354            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
355        }
356        return View.NO_ID;
357    }
358
359    /**
360     * Removed an accessibility interaction connection interface for a given window.
361     * @param windowToken The window token to which a connection is removed.
362     *
363     * @hide
364     */
365    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
366        try {
367            mService.removeAccessibilityInteractionConnection(windowToken);
368        } catch (RemoteException re) {
369            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
370        }
371    }
372}
373