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 static android.util.Config.LOGV;
20
21import android.content.Context;
22import android.content.pm.ServiceInfo;
23import android.os.Binder;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.SystemClock;
31import android.util.Log;
32
33import java.util.Collections;
34import java.util.List;
35
36/**
37 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
38 * Such events are generated when something notable happens in the user interface,
39 * for example an {@link android.app.Activity} starts, the focus or selection of a
40 * {@link android.view.View} changes etc. Parties interested in handling accessibility
41 * events implement and register an accessibility service which extends
42 * {@link android.accessibilityservice.AccessibilityService}.
43 *
44 * @see AccessibilityEvent
45 * @see android.accessibilityservice.AccessibilityService
46 * @see android.content.Context#getSystemService
47 */
48public final class AccessibilityManager {
49    private static final String LOG_TAG = "AccessibilityManager";
50
51    static final Object sInstanceSync = new Object();
52
53    private static AccessibilityManager sInstance;
54
55    private static final int DO_SET_ENABLED = 10;
56
57    final IAccessibilityManager mService;
58
59    final Handler mHandler;
60
61    boolean mIsEnabled;
62
63    final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
64        public void setEnabled(boolean enabled) {
65            mHandler.obtainMessage(DO_SET_ENABLED, enabled ? 1 : 0, 0).sendToTarget();
66        }
67    };
68
69    class MyHandler extends Handler {
70
71        MyHandler(Looper mainLooper) {
72            super(mainLooper);
73        }
74
75        @Override
76        public void handleMessage(Message message) {
77            switch (message.what) {
78                case DO_SET_ENABLED :
79                    synchronized (mHandler) {
80                        mIsEnabled = (message.arg1 == 1);
81                    }
82                    return;
83                default :
84                    Log.w(LOG_TAG, "Unknown message type: " + message.what);
85            }
86        }
87    }
88
89    /**
90     * Get an AccessibilityManager instance (create one if necessary).
91     *
92     * @hide
93     */
94    public static AccessibilityManager getInstance(Context context) {
95        synchronized (sInstanceSync) {
96            if (sInstance == null) {
97                sInstance = new AccessibilityManager(context);
98            }
99        }
100        return sInstance;
101    }
102
103    /**
104     * Create an instance.
105     *
106     * @param context A {@link Context}.
107     */
108    private AccessibilityManager(Context context) {
109        mHandler = new MyHandler(context.getMainLooper());
110        IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
111        mService = IAccessibilityManager.Stub.asInterface(iBinder);
112        try {
113            mService.addClient(mClient);
114        } catch (RemoteException re) {
115            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
116        }
117    }
118
119    /**
120     * Returns if the {@link AccessibilityManager} is enabled.
121     *
122     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
123     */
124    public boolean isEnabled() {
125        synchronized (mHandler) {
126            return mIsEnabled;
127        }
128    }
129
130    /**
131     * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not
132     * enabled the call is a NOOP.
133     *
134     * @param event The {@link AccessibilityEvent}.
135     *
136     * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent}
137     *         while accessibility is not enabled.
138     */
139    public void sendAccessibilityEvent(AccessibilityEvent event) {
140        if (!mIsEnabled) {
141            throw new IllegalStateException("Accessibility off. Did you forget to check that?");
142        }
143        boolean doRecycle = false;
144        try {
145            event.setEventTime(SystemClock.uptimeMillis());
146            // it is possible that this manager is in the same process as the service but
147            // client using it is called through Binder from another process. Example: MMS
148            // app adds a SMS notification and the NotificationManagerService calls this method
149            long identityToken = Binder.clearCallingIdentity();
150            doRecycle = mService.sendAccessibilityEvent(event);
151            Binder.restoreCallingIdentity(identityToken);
152            if (LOGV) {
153                Log.i(LOG_TAG, event + " sent");
154            }
155        } catch (RemoteException re) {
156            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
157        } finally {
158            if (doRecycle) {
159                event.recycle();
160            }
161        }
162    }
163
164    /**
165     * Requests interruption of the accessibility feedback from all accessibility services.
166     */
167    public void interrupt() {
168        if (!mIsEnabled) {
169            throw new IllegalStateException("Accessibility off. Did you forget to check that?");
170        }
171        try {
172            mService.interrupt();
173            if (LOGV) {
174                Log.i(LOG_TAG, "Requested interrupt from all services");
175            }
176        } catch (RemoteException re) {
177            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
178        }
179    }
180
181    /**
182     * Returns the {@link ServiceInfo}s of the installed accessibility services.
183     *
184     * @return An unmodifiable list with {@link ServiceInfo}s.
185     */
186    public List<ServiceInfo> getAccessibilityServiceList() {
187        List<ServiceInfo> services = null;
188        try {
189            services = mService.getAccessibilityServiceList();
190            if (LOGV) {
191                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
192            }
193        } catch (RemoteException re) {
194            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
195        }
196        return Collections.unmodifiableList(services);
197    }
198}
199