blob: d96369b101fbfd4c572f8a45e3ea892c4db888cd [file] [log] [blame]
svetoslavganov75986cf2009-05-14 22:28:01 -07001/*
2 ** Copyright 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
Jeff Brown6e6cd7a2011-03-30 03:27:08 -070017package com.android.server.accessibility;
svetoslavganov75986cf2009-05-14 22:28:01 -070018
Dianne Hackborn21f1bd12010-02-19 17:02:21 -080019import com.android.internal.content.PackageMonitor;
svetoslavganov75986cf2009-05-14 22:28:01 -070020import com.android.internal.os.HandlerCaller;
21import com.android.internal.os.HandlerCaller.SomeArgs;
Jeff Brown0029c662011-03-30 02:25:18 -070022import com.android.server.wm.WindowManagerService;
svetoslavganov75986cf2009-05-14 22:28:01 -070023
24import android.accessibilityservice.AccessibilityService;
25import android.accessibilityservice.AccessibilityServiceInfo;
26import android.accessibilityservice.IAccessibilityServiceConnection;
27import android.accessibilityservice.IEventListener;
Dianne Hackborndd9b82c2009-09-03 00:18:47 -070028import android.app.PendingIntent;
svetoslavganov75986cf2009-05-14 22:28:01 -070029import android.content.BroadcastReceiver;
30import android.content.ComponentName;
31import android.content.ContentResolver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.ServiceConnection;
36import android.content.pm.PackageManager;
37import android.content.pm.ResolveInfo;
38import android.content.pm.ServiceInfo;
39import android.database.ContentObserver;
40import android.net.Uri;
41import android.os.Binder;
42import android.os.DeadObjectException;
43import android.os.Handler;
44import android.os.IBinder;
45import android.os.Message;
46import android.os.RemoteException;
Jeff Brown0029c662011-03-30 02:25:18 -070047import android.os.ServiceManager;
svetoslavganov75986cf2009-05-14 22:28:01 -070048import android.provider.Settings;
49import android.text.TextUtils;
50import android.text.TextUtils.SimpleStringSplitter;
Joe Onorato8a9b2202010-02-26 18:56:32 -080051import android.util.Slog;
svetoslavganov75986cf2009-05-14 22:28:01 -070052import android.util.SparseArray;
53import android.view.accessibility.AccessibilityEvent;
54import android.view.accessibility.IAccessibilityManager;
55import android.view.accessibility.IAccessibilityManagerClient;
56
57import java.util.ArrayList;
58import java.util.Arrays;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070059import java.util.Collections;
svetoslavganov75986cf2009-05-14 22:28:01 -070060import java.util.HashMap;
61import java.util.HashSet;
Dianne Hackborn21f1bd12010-02-19 17:02:21 -080062import java.util.Iterator;
svetoslavganov75986cf2009-05-14 22:28:01 -070063import java.util.List;
64import java.util.Map;
65import java.util.Set;
66
67/**
68 * This class is instantiated by the system as a system level service and can be
69 * accessed only by the system. The task of this service is to be a centralized
70 * event dispatch for {@link AccessibilityEvent}s generated across all processes
71 * on the device. Events are dispatched to {@link AccessibilityService}s.
72 *
73 * @hide
74 */
75public class AccessibilityManagerService extends IAccessibilityManager.Stub
76 implements HandlerCaller.Callback {
77
Svetoslav Ganov736c2752011-04-22 18:30:36 -070078 private static final boolean DEBUG = false;
79
svetoslavganov75986cf2009-05-14 22:28:01 -070080 private static final String LOG_TAG = "AccessibilityManagerService";
81
82 private static int sIdCounter = 0;
83
84 private static final int OWN_PROCESS_ID = android.os.Process.myPid();
85
86 private static final int DO_SET_SERVICE_INFO = 10;
87
88 final HandlerCaller mCaller;
89
90 final Context mContext;
91
92 final Object mLock = new Object();
93
94 final List<Service> mServices = new ArrayList<Service>();
95
96 final List<IAccessibilityManagerClient> mClients =
97 new ArrayList<IAccessibilityManagerClient>();
98
99 final Map<ComponentName, Service> mComponentNameToServiceMap =
100 new HashMap<ComponentName, Service>();
101
102 private final List<ServiceInfo> mInstalledServices = new ArrayList<ServiceInfo>();
103
104 private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
105
106 private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
107
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700108 private final SparseArray<List<ServiceInfo>> mFeedbackTypeToEnabledServicesMap =
109 new SparseArray<List<ServiceInfo>>();
110
svetoslavganov75986cf2009-05-14 22:28:01 -0700111 private PackageManager mPackageManager;
112
113 private int mHandledFeedbackTypes = 0;
114
115 private boolean mIsEnabled;
Jeff Brown0029c662011-03-30 02:25:18 -0700116 private AccessibilityInputFilter mInputFilter;
svetoslavganov75986cf2009-05-14 22:28:01 -0700117
118 /**
119 * Handler for delayed event dispatch.
120 */
121 private Handler mHandler = new Handler() {
122
123 @Override
124 public void handleMessage(Message message) {
125 Service service = (Service) message.obj;
126 int eventType = message.arg1;
127
128 synchronized (mLock) {
129 notifyEventListenerLocked(service, eventType);
130 AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
131 service.mPendingEvents.remove(eventType);
132 tryRecycleLocked(oldEvent);
133 }
134 }
135 };
136
137 /**
138 * Creates a new instance.
139 *
140 * @param context A {@link Context} instance.
141 */
Jeff Brown6e6cd7a2011-03-30 03:27:08 -0700142 public AccessibilityManagerService(Context context) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700143 mContext = context;
144 mPackageManager = mContext.getPackageManager();
145 mCaller = new HandlerCaller(context, this);
146
147 registerPackageChangeAndBootCompletedBroadcastReceiver();
148 registerSettingsContentObservers();
svetoslavganov75986cf2009-05-14 22:28:01 -0700149 }
150
151 /**
152 * Registers a {@link BroadcastReceiver} for the events of
153 * adding/changing/removing/restarting a package and boot completion.
154 */
155 private void registerPackageChangeAndBootCompletedBroadcastReceiver() {
156 Context context = mContext;
157
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800158 PackageMonitor monitor = new PackageMonitor() {
svetoslavganov75986cf2009-05-14 22:28:01 -0700159 @Override
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800160 public void onSomePackagesChanged() {
svetoslavganov75986cf2009-05-14 22:28:01 -0700161 synchronized (mLock) {
162 populateAccessibilityServiceListLocked();
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800163 manageServicesLocked();
164 }
165 }
166
167 @Override
168 public boolean onHandleForceStop(Intent intent, String[] packages,
169 int uid, boolean doit) {
170 synchronized (mLock) {
171 boolean changed = false;
172 Iterator<ComponentName> it = mEnabledServices.iterator();
173 while (it.hasNext()) {
174 ComponentName comp = it.next();
175 String compPkg = comp.getPackageName();
176 for (String pkg : packages) {
177 if (compPkg.equals(pkg)) {
178 if (!doit) {
179 return true;
180 }
181 it.remove();
182 changed = true;
183 }
184 }
185 }
186 if (changed) {
187 it = mEnabledServices.iterator();
188 StringBuilder str = new StringBuilder();
189 while (it.hasNext()) {
190 if (str.length() > 0) {
191 str.append(':');
192 }
193 str.append(it.next().flattenToShortString());
194 }
195 Settings.Secure.putString(mContext.getContentResolver(),
196 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
197 str.toString());
198 manageServicesLocked();
199 }
200 return false;
201 }
202 }
203
204 @Override
205 public void onReceive(Context context, Intent intent) {
206 if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
207 synchronized (mLock) {
208 populateAccessibilityServiceListLocked();
svetoslavganov75986cf2009-05-14 22:28:01 -0700209
Svetoslav Ganov714cff02010-02-17 19:36:28 -0800210 // get the accessibility enabled setting on boot
svetoslavganov75986cf2009-05-14 22:28:01 -0700211 mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
212 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
Svetoslav Ganov714cff02010-02-17 19:36:28 -0800213
214 // if accessibility is enabled inform our clients we are on
215 if (mIsEnabled) {
216 updateClientsLocked();
217 }
Svetoslav Ganov714cff02010-02-17 19:36:28 -0800218
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800219 manageServicesLocked();
220 }
221
222 return;
svetoslavganov75986cf2009-05-14 22:28:01 -0700223 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800224
225 super.onReceive(context, intent);
svetoslavganov75986cf2009-05-14 22:28:01 -0700226 }
227 };
228
229 // package changes
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800230 monitor.register(context, true);
svetoslavganov75986cf2009-05-14 22:28:01 -0700231
232 // boot completed
233 IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800234 mContext.registerReceiver(monitor, bootFiler);
svetoslavganov75986cf2009-05-14 22:28:01 -0700235 }
236
237 /**
238 * {@link ContentObserver}s for {@link Settings.Secure#ACCESSIBILITY_ENABLED}
239 * and {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} settings.
240 */
241 private void registerSettingsContentObservers() {
242 ContentResolver contentResolver = mContext.getContentResolver();
243
244 Uri enabledUri = Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_ENABLED);
245 contentResolver.registerContentObserver(enabledUri, false,
246 new ContentObserver(new Handler()) {
247 @Override
248 public void onChange(boolean selfChange) {
249 super.onChange(selfChange);
250
svetoslavganov75986cf2009-05-14 22:28:01 -0700251 synchronized (mLock) {
Svetoslav Ganov51f36f22010-12-18 16:20:53 -0800252 mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
253 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
svetoslavganov75986cf2009-05-14 22:28:01 -0700254 if (mIsEnabled) {
255 manageServicesLocked();
256 } else {
257 unbindAllServicesLocked();
258 }
259 updateClientsLocked();
260 }
261 }
262 });
263
264 Uri providersUri =
265 Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
266 contentResolver.registerContentObserver(providersUri, false,
267 new ContentObserver(new Handler()) {
268 @Override
269 public void onChange(boolean selfChange) {
270 super.onChange(selfChange);
271
272 synchronized (mLock) {
273 manageServicesLocked();
274 }
275 }
276 });
277 }
278
Svetoslav Ganovdd64a9b2010-04-13 18:41:17 -0700279 public boolean addClient(IAccessibilityManagerClient client) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700280 synchronized (mLock) {
Svetoslav Ganovdd64a9b2010-04-13 18:41:17 -0700281 mClients.add(client);
282 return mIsEnabled;
svetoslavganov75986cf2009-05-14 22:28:01 -0700283 }
284 }
285
286 public boolean sendAccessibilityEvent(AccessibilityEvent event) {
287 synchronized (mLock) {
288 notifyAccessibilityServicesDelayedLocked(event, false);
289 notifyAccessibilityServicesDelayedLocked(event, true);
290 }
291 // event not scheduled for dispatch => recycle
292 if (mHandledFeedbackTypes == 0) {
293 event.recycle();
294 } else {
295 mHandledFeedbackTypes = 0;
296 }
297
298 return (OWN_PROCESS_ID != Binder.getCallingPid());
299 }
300
301 public List<ServiceInfo> getAccessibilityServiceList() {
302 synchronized (mLock) {
303 return mInstalledServices;
304 }
305 }
306
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700307 public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
308 synchronized (mLock) {
309 List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType);
310 if (enabledServices == null) {
311 return Collections.emptyList();
312 }
313 return enabledServices;
314 }
315 }
316
svetoslavganov75986cf2009-05-14 22:28:01 -0700317 public void interrupt() {
318 synchronized (mLock) {
319 for (int i = 0, count = mServices.size(); i < count; i++) {
320 Service service = mServices.get(i);
321 try {
322 service.mServiceInterface.onInterrupt();
323 } catch (RemoteException re) {
324 if (re instanceof DeadObjectException) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800325 Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
svetoslavganov75986cf2009-05-14 22:28:01 -0700326 if (removeDeadServiceLocked(service)) {
327 count--;
328 i--;
329 }
330 } else {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800331 Slog.e(LOG_TAG, "Error during sending interrupt request to "
svetoslavganov75986cf2009-05-14 22:28:01 -0700332 + service.mService, re);
333 }
334 }
335 }
336 }
337 }
338
339 public void executeMessage(Message message) {
340 switch (message.what) {
341 case DO_SET_SERVICE_INFO:
342 SomeArgs arguments = ((SomeArgs) message.obj);
343
344 AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1;
345 Service service = (Service) arguments.arg2;
346
347 synchronized (mLock) {
348 service.mEventTypes = info.eventTypes;
349 service.mFeedbackType = info.feedbackType;
350 String[] packageNames = info.packageNames;
351 if (packageNames != null) {
352 service.mPackageNames.addAll(Arrays.asList(packageNames));
353 }
354 service.mNotificationTimeout = info.notificationTimeout;
355 service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700356
357 updateStateOnEnabledService(service);
svetoslavganov75986cf2009-05-14 22:28:01 -0700358 }
359 return;
360 default:
Joe Onorato8a9b2202010-02-26 18:56:32 -0800361 Slog.w(LOG_TAG, "Unknown message type: " + message.what);
svetoslavganov75986cf2009-05-14 22:28:01 -0700362 }
363 }
364
365 /**
366 * Populates the cached list of installed {@link AccessibilityService}s.
367 */
368 private void populateAccessibilityServiceListLocked() {
369 mInstalledServices.clear();
370
371 List<ResolveInfo> installedServices = mPackageManager.queryIntentServices(
372 new Intent(AccessibilityService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
373
374 for (int i = 0, count = installedServices.size(); i < count; i++) {
375 mInstalledServices.add(installedServices.get(i).serviceInfo);
376 }
377 }
378
379 /**
380 * Performs {@link AccessibilityService}s delayed notification. The delay is configurable
381 * and denotes the period after the last event before notifying the service.
382 *
383 * @param event The event.
384 * @param isDefault True to notify default listeners, not default services.
385 */
386 private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
387 boolean isDefault) {
Charles Chen85b598b2009-07-29 17:23:50 -0700388 try {
389 for (int i = 0, count = mServices.size(); i < count; i++) {
390 Service service = mServices.get(i);
svetoslavganov75986cf2009-05-14 22:28:01 -0700391
Charles Chen85b598b2009-07-29 17:23:50 -0700392 if (service.mIsDefault == isDefault) {
393 if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
394 mHandledFeedbackTypes |= service.mFeedbackType;
395 notifyAccessibilityServiceDelayedLocked(service, event);
396 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700397 }
398 }
Charles Chen85b598b2009-07-29 17:23:50 -0700399 } catch (IndexOutOfBoundsException oobe) {
400 // An out of bounds exception can happen if services are going away
401 // as the for loop is running. If that happens, just bail because
402 // there are no more services to notify.
403 return;
svetoslavganov75986cf2009-05-14 22:28:01 -0700404 }
405 }
406
407 /**
408 * Performs an {@link AccessibilityService} delayed notification. The delay is configurable
409 * and denotes the period after the last event before notifying the service.
410 *
411 * @param service The service.
412 * @param event The event.
413 */
414 private void notifyAccessibilityServiceDelayedLocked(Service service,
415 AccessibilityEvent event) {
416 synchronized (mLock) {
417 int eventType = event.getEventType();
418 AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
419 service.mPendingEvents.put(eventType, event);
420
421 int what = eventType | (service.mId << 16);
422 if (oldEvent != null) {
423 mHandler.removeMessages(what);
424 tryRecycleLocked(oldEvent);
425 }
426
427 Message message = mHandler.obtainMessage(what, service);
428 message.arg1 = event.getEventType();
429 mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
430 }
431 }
432
433 /**
434 * Recycles an event if it can be safely recycled. The condition is that no
435 * not notified service is interested in the event.
436 *
437 * @param event The event.
438 */
439 private void tryRecycleLocked(AccessibilityEvent event) {
Charles Chenbbc19342009-07-24 16:06:09 -0700440 if (event == null) {
441 return;
442 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700443 int eventType = event.getEventType();
444 List<Service> services = mServices;
445
446 // linear in the number of service which is not large
447 for (int i = 0, count = services.size(); i < count; i++) {
448 Service service = services.get(i);
449 if (service.mPendingEvents.get(eventType) == event) {
450 return;
451 }
452 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700453 event.recycle();
454 }
455
456 /**
457 * Notifies a service for a scheduled event given the event type.
458 *
459 * @param service The service.
460 * @param eventType The type of the event to dispatch.
461 */
462 private void notifyEventListenerLocked(Service service, int eventType) {
463 IEventListener listener = service.mServiceInterface;
464 AccessibilityEvent event = service.mPendingEvents.get(eventType);
465
466 try {
467 listener.onAccessibilityEvent(event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700468 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800469 Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
svetoslavganov75986cf2009-05-14 22:28:01 -0700470 }
471 } catch (RemoteException re) {
472 if (re instanceof DeadObjectException) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800473 Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
Svetoslav Ganov51f36f22010-12-18 16:20:53 -0800474 removeDeadServiceLocked(service);
svetoslavganov75986cf2009-05-14 22:28:01 -0700475 } else {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800476 Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
svetoslavganov75986cf2009-05-14 22:28:01 -0700477 }
478 }
479 }
480
481 /**
482 * Removes a dead service.
483 *
484 * @param service The service.
485 * @return True if the service was removed, false otherwise.
486 */
487 private boolean removeDeadServiceLocked(Service service) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700488 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800489 Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
svetoslavganov75986cf2009-05-14 22:28:01 -0700490 }
Svetoslav Ganov51f36f22010-12-18 16:20:53 -0800491 mHandler.removeMessages(service.mId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700492 updateStateOnDisabledService(service);
Svetoslav Ganov51f36f22010-12-18 16:20:53 -0800493 return mServices.remove(service);
svetoslavganov75986cf2009-05-14 22:28:01 -0700494 }
495
496 /**
497 * Determines if given event can be dispatched to a service based on the package of the
498 * event source and already notified services for that event type. Specifically, a
499 * service is notified if it is interested in events from the package and no other service
500 * providing the same feedback type has been notified. Exception are services the
501 * provide generic feedback (feedback type left as a safety net for unforeseen feedback
502 * types) which are always notified.
503 *
504 * @param service The potential receiver.
505 * @param event The event.
506 * @param handledFeedbackTypes The feedback types for which services have been notified.
507 * @return True if the listener should be notified, false otherwise.
508 */
509 private boolean canDispathEventLocked(Service service, AccessibilityEvent event,
510 int handledFeedbackTypes) {
511
512 if (!service.isConfigured()) {
513 return false;
514 }
515
516 if (!service.mService.isBinderAlive()) {
517 removeDeadServiceLocked(service);
518 return false;
519 }
520
521 int eventType = event.getEventType();
522 if ((service.mEventTypes & eventType) != eventType) {
523 return false;
524 }
525
526 Set<String> packageNames = service.mPackageNames;
527 CharSequence packageName = event.getPackageName();
528
529 if (packageNames.isEmpty() || packageNames.contains(packageName)) {
530 int feedbackType = service.mFeedbackType;
531 if ((handledFeedbackTypes & feedbackType) != feedbackType
532 || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
533 return true;
534 }
535 }
536
537 return false;
538 }
539
540 /**
541 * Manages services by starting enabled ones and stopping disabled ones.
542 */
543 private void manageServicesLocked() {
544 populateEnabledServicesLocked(mEnabledServices);
545 updateServicesStateLocked(mInstalledServices, mEnabledServices);
546 }
547
548 /**
549 * Unbinds all bound services.
550 */
551 private void unbindAllServicesLocked() {
552 List<Service> services = mServices;
553
554 for (int i = 0, count = services.size(); i < count; i++) {
555 Service service = services.get(i);
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800556 if (service.unbind()) {
557 i--;
558 count--;
559 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700560 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700561 }
562
563 /**
564 * Populates a list with the {@link ComponentName}s of all enabled
565 * {@link AccessibilityService}s.
566 *
567 * @param enabledServices The list.
568 */
569 private void populateEnabledServicesLocked(Set<ComponentName> enabledServices) {
570 enabledServices.clear();
571
572 String servicesValue = Settings.Secure.getString(mContext.getContentResolver(),
573 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
574
575 if (servicesValue != null) {
576 TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
577 splitter.setString(servicesValue);
578 while (splitter.hasNext()) {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800579 String str = splitter.next();
580 if (str == null || str.length() <= 0) {
581 continue;
582 }
583 ComponentName enabledService = ComponentName.unflattenFromString(str);
584 if (enabledService != null) {
585 enabledServices.add(enabledService);
586 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700587 }
588 }
589 }
590
591 /**
592 * Updates the state of each service by starting (or keeping running) enabled ones and
593 * stopping the rest.
594 *
595 * @param installedServices All installed {@link AccessibilityService}s.
596 * @param enabledServices The {@link ComponentName}s of the enabled services.
597 */
598 private void updateServicesStateLocked(List<ServiceInfo> installedServices,
599 Set<ComponentName> enabledServices) {
600
601 Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap;
Svetoslav Ganov714cff02010-02-17 19:36:28 -0800602 boolean isEnabled = mIsEnabled;
svetoslavganov75986cf2009-05-14 22:28:01 -0700603
604 for (int i = 0, count = installedServices.size(); i < count; i++) {
605 ServiceInfo intalledService = installedServices.get(i);
606 ComponentName componentName = new ComponentName(intalledService.packageName,
607 intalledService.name);
608 Service service = componentNameToServiceMap.get(componentName);
609
Svetoslav Ganovf2245aa2010-12-28 15:48:52 -0800610 if (isEnabled) {
Svetoslav Ganov563d7842011-01-06 17:40:26 -0800611 if (enabledServices.contains(componentName)) {
612 if (service == null) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700613 service = new Service(componentName, intalledService);
Svetoslav Ganov563d7842011-01-06 17:40:26 -0800614 }
615 service.bind();
616 } else if (!enabledServices.contains(componentName)) {
617 if (service != null) {
618 service.unbind();
619 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700620 }
621 } else {
622 if (service != null) {
623 service.unbind();
svetoslavganov75986cf2009-05-14 22:28:01 -0700624 }
625 }
626 }
627 }
628
629 /**
630 * Updates the state of {@link android.view.accessibility.AccessibilityManager} clients.
631 */
632 private void updateClientsLocked() {
633 for (int i = 0, count = mClients.size(); i < count; i++) {
634 try {
635 mClients.get(i).setEnabled(mIsEnabled);
636 } catch (RemoteException re) {
637 mClients.remove(i);
638 count--;
Svetoslav Ganovfb606da2010-02-18 10:54:36 -0800639 i--;
svetoslavganov75986cf2009-05-14 22:28:01 -0700640 }
641 }
642 }
643
644 /**
Svetoslav Ganova3e261d2011-04-27 12:06:23 -0700645 * Sets the input filter state. If the filter is in enabled it is registered
646 * in the window manager, otherwise the filter is removed from the latter.
647 *
648 * @param enabled Whether the input filter is enabled.
Jeff Brown0029c662011-03-30 02:25:18 -0700649 */
Svetoslav Ganova3e261d2011-04-27 12:06:23 -0700650 private void setInputFilterEnabledLocked(boolean enabled) {
Jeff Brown0029c662011-03-30 02:25:18 -0700651 WindowManagerService wm = (WindowManagerService)ServiceManager.getService(
652 Context.WINDOW_SERVICE);
653 if (wm != null) {
Svetoslav Ganova3e261d2011-04-27 12:06:23 -0700654 if (enabled) {
Jeff Brown0029c662011-03-30 02:25:18 -0700655 if (mInputFilter == null) {
656 mInputFilter = new AccessibilityInputFilter(mContext);
657 }
658 wm.setInputFilter(mInputFilter);
659 } else {
660 wm.setInputFilter(null);
661 }
662 }
663 }
664
665 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700666 * Updates the set of enabled services for a given feedback type and
667 * if more than one of them provides spoken feedback enables touch
668 * exploration.
669 *
670 * @param service An enable service.
671 */
672 private void updateStateOnEnabledService(Service service) {
673 int feedbackType = service.mFeedbackType;
674 List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType);
675 if (enabledServices == null) {
676 enabledServices = new ArrayList<ServiceInfo>();
677 mFeedbackTypeToEnabledServicesMap.put(feedbackType, enabledServices);
678 }
679 enabledServices.add(service.mServiceInfo);
680
681 // We enable touch exploration if at least one
682 // enabled service provides spoken feedback.
683 if (enabledServices.size() > 0
684 && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
685 updateClientsLocked();
Svetoslav Ganova3e261d2011-04-27 12:06:23 -0700686 setInputFilterEnabledLocked(true);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700687 }
688 }
689
690 private void updateStateOnDisabledService(Service service) {
691 List<ServiceInfo> enabledServices =
692 mFeedbackTypeToEnabledServicesMap.get(service.mFeedbackType);
693 if (enabledServices == null) {
694 return;
695 }
696 enabledServices.remove(service.mServiceInfo);
697 // We disable touch exploration if no
698 // enabled service provides spoken feedback.
699 if (enabledServices.isEmpty()
700 && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
701 updateClientsLocked();
Svetoslav Ganova3e261d2011-04-27 12:06:23 -0700702 setInputFilterEnabledLocked(false);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700703 }
704 }
705
706 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700707 * This class represents an accessibility service. It stores all per service
708 * data required for the service management, provides API for starting/stopping the
709 * service and is responsible for adding/removing the service in the data structures
710 * for service management. The class also exposes configuration interface that is
711 * passed to the service it represents as soon it is bound. It also serves as the
712 * connection for the service.
713 */
714 class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection {
715 int mId = 0;
716
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700717 ServiceInfo mServiceInfo;
718
svetoslavganov75986cf2009-05-14 22:28:01 -0700719 IBinder mService;
720
721 IEventListener mServiceInterface;
722
723 int mEventTypes;
724
725 int mFeedbackType;
726
727 Set<String> mPackageNames = new HashSet<String>();
728
729 boolean mIsDefault;
730
731 long mNotificationTimeout;
732
733 boolean mIsActive;
734
735 ComponentName mComponentName;
736
737 Intent mIntent;
738
739 // the events pending events to be dispatched to this service
740 final SparseArray<AccessibilityEvent> mPendingEvents =
741 new SparseArray<AccessibilityEvent>();
742
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700743 Service(ComponentName componentName, ServiceInfo serviceInfo) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700744 mId = sIdCounter++;
745 mComponentName = componentName;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700746 mServiceInfo = serviceInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700747 mIntent = new Intent().setComponent(mComponentName);
Dianne Hackborndd9b82c2009-09-03 00:18:47 -0700748 mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
749 com.android.internal.R.string.accessibility_binding_label);
750 mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
751 mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
svetoslavganov75986cf2009-05-14 22:28:01 -0700752 }
753
754 /**
755 * Binds to the accessibility service.
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800756 *
757 * @return True if binding is successful.
svetoslavganov75986cf2009-05-14 22:28:01 -0700758 */
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800759 public boolean bind() {
svetoslavganov75986cf2009-05-14 22:28:01 -0700760 if (mService == null) {
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800761 return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
svetoslavganov75986cf2009-05-14 22:28:01 -0700762 }
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800763 return false;
svetoslavganov75986cf2009-05-14 22:28:01 -0700764 }
765
766 /**
767 * Unbinds form the accessibility service and removes it from the data
768 * structures for service management.
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800769 *
770 * @return True if unbinding is successful.
svetoslavganov75986cf2009-05-14 22:28:01 -0700771 */
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800772 public boolean unbind() {
svetoslavganov75986cf2009-05-14 22:28:01 -0700773 if (mService != null) {
Svetoslav Ganov6f089b82011-01-11 15:04:40 -0800774 mService = null;
svetoslavganov75986cf2009-05-14 22:28:01 -0700775 mContext.unbindService(this);
Svetoslav Ganov563d7842011-01-06 17:40:26 -0800776 mComponentNameToServiceMap.remove(mComponentName);
777 mServices.remove(this);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700778 updateStateOnDisabledService(this);
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800779 return true;
svetoslavganov75986cf2009-05-14 22:28:01 -0700780 }
Svetoslav Ganov6ff5f102011-01-10 13:12:55 -0800781 return false;
svetoslavganov75986cf2009-05-14 22:28:01 -0700782 }
783
784 /**
785 * Returns if the service is configured i.e. at least event types of interest
786 * and feedback type must be set.
787 *
788 * @return True if the service is configured, false otherwise.
789 */
790 public boolean isConfigured() {
791 return (mEventTypes != 0 && mFeedbackType != 0);
792 }
793
794 public void setServiceInfo(AccessibilityServiceInfo info) {
795 mCaller.obtainMessageOO(DO_SET_SERVICE_INFO, info, this).sendToTarget();
796 }
797
798 public void onServiceConnected(ComponentName componentName, IBinder service) {
799 mService = service;
800 mServiceInterface = IEventListener.Stub.asInterface(service);
801
802 try {
803 mServiceInterface.setConnection(this);
804 synchronized (mLock) {
805 if (!mServices.contains(this)) {
806 mServices.add(this);
807 mComponentNameToServiceMap.put(componentName, this);
808 }
809 }
810 } catch (RemoteException re) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800811 Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
svetoslavganov75986cf2009-05-14 22:28:01 -0700812 }
813 }
814
815 public void onServiceDisconnected(ComponentName componentName) {
816 synchronized (mLock) {
817 Service service = mComponentNameToServiceMap.remove(componentName);
818 mServices.remove(service);
819 }
820 }
821 }
822}