AccessibilityManagerService.java revision a3e261d506551713477adb2fd47d1a65b94e64d8
1/*
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
17package com.android.server.accessibility;
18
19import com.android.internal.content.PackageMonitor;
20import com.android.internal.os.HandlerCaller;
21import com.android.internal.os.HandlerCaller.SomeArgs;
22import com.android.server.wm.WindowManagerService;
23
24import android.accessibilityservice.AccessibilityService;
25import android.accessibilityservice.AccessibilityServiceInfo;
26import android.accessibilityservice.IAccessibilityServiceConnection;
27import android.accessibilityservice.IEventListener;
28import android.app.PendingIntent;
29import 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;
47import android.os.ServiceManager;
48import android.provider.Settings;
49import android.text.TextUtils;
50import android.text.TextUtils.SimpleStringSplitter;
51import android.util.Slog;
52import 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;
59import java.util.Collections;
60import java.util.HashMap;
61import java.util.HashSet;
62import java.util.Iterator;
63import 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
78    private static final boolean DEBUG = false;
79
80    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
108    private final SparseArray<List<ServiceInfo>> mFeedbackTypeToEnabledServicesMap =
109        new SparseArray<List<ServiceInfo>>();
110
111    private PackageManager mPackageManager;
112
113    private int mHandledFeedbackTypes = 0;
114
115    private boolean mIsEnabled;
116    private AccessibilityInputFilter mInputFilter;
117
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     */
142    public AccessibilityManagerService(Context context) {
143        mContext = context;
144        mPackageManager = mContext.getPackageManager();
145        mCaller = new HandlerCaller(context, this);
146
147        registerPackageChangeAndBootCompletedBroadcastReceiver();
148        registerSettingsContentObservers();
149    }
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
158        PackageMonitor monitor = new PackageMonitor() {
159            @Override
160            public void onSomePackagesChanged() {
161                synchronized (mLock) {
162                    populateAccessibilityServiceListLocked();
163                    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();
209
210                        // get the accessibility enabled setting on boot
211                        mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
212                                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
213
214                        // if accessibility is enabled inform our clients we are on
215                        if (mIsEnabled) {
216                            updateClientsLocked();
217                        }
218
219                        manageServicesLocked();
220                    }
221
222                    return;
223                }
224
225                super.onReceive(context, intent);
226            }
227        };
228
229        // package changes
230        monitor.register(context, true);
231
232        // boot completed
233        IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
234        mContext.registerReceiver(monitor, bootFiler);
235    }
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
251                    synchronized (mLock) {
252                        mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
253                                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
254                        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
279    public boolean addClient(IAccessibilityManagerClient client) {
280        synchronized (mLock) {
281            mClients.add(client);
282            return mIsEnabled;
283        }
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
307    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
317    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) {
325                        Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
326                        if (removeDeadServiceLocked(service)) {
327                            count--;
328                            i--;
329                        }
330                    } else {
331                        Slog.e(LOG_TAG, "Error during sending interrupt request to "
332                                + 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;
356
357                    updateStateOnEnabledService(service);
358                }
359                return;
360            default:
361                Slog.w(LOG_TAG, "Unknown message type: " + message.what);
362        }
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) {
388        try {
389            for (int i = 0, count = mServices.size(); i < count; i++) {
390                Service service = mServices.get(i);
391
392                if (service.mIsDefault == isDefault) {
393                    if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
394                        mHandledFeedbackTypes |= service.mFeedbackType;
395                        notifyAccessibilityServiceDelayedLocked(service, event);
396                    }
397                }
398            }
399        } 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;
404        }
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) {
440        if (event == null) {
441            return;
442        }
443        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        }
453        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);
468            if (DEBUG) {
469                Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
470            }
471        } catch (RemoteException re) {
472            if (re instanceof DeadObjectException) {
473                Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
474                removeDeadServiceLocked(service);
475            } else {
476                Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
477            }
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) {
488        if (DEBUG) {
489            Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
490        }
491        mHandler.removeMessages(service.mId);
492        updateStateOnDisabledService(service);
493        return mServices.remove(service);
494    }
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);
556            if (service.unbind()) {
557                i--;
558                count--;
559            }
560        }
561    }
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()) {
579                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                }
587            }
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;
602        boolean isEnabled = mIsEnabled;
603
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
610            if (isEnabled) {
611                if (enabledServices.contains(componentName)) {
612                    if (service == null) {
613                        service = new Service(componentName, intalledService);
614                    }
615                    service.bind();
616                } else if (!enabledServices.contains(componentName)) {
617                    if (service != null) {
618                        service.unbind();
619                    }
620                }
621            } else {
622                if (service != null) {
623                    service.unbind();
624                }
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--;
639                i--;
640            }
641        }
642    }
643
644    /**
645     * 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.
649     */
650    private void setInputFilterEnabledLocked(boolean enabled) {
651        WindowManagerService wm = (WindowManagerService)ServiceManager.getService(
652                Context.WINDOW_SERVICE);
653        if (wm != null) {
654            if (enabled) {
655                if (mInputFilter == null) {
656                    mInputFilter = new AccessibilityInputFilter(mContext);
657                }
658                wm.setInputFilter(mInputFilter);
659            } else {
660                wm.setInputFilter(null);
661            }
662        }
663    }
664
665    /**
666     * 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();
686            setInputFilterEnabledLocked(true);
687        }
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();
702            setInputFilterEnabledLocked(false);
703        }
704    }
705
706    /**
707     * 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
717        ServiceInfo mServiceInfo;
718
719        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
743        Service(ComponentName componentName, ServiceInfo serviceInfo) {
744            mId = sIdCounter++;
745            mComponentName = componentName;
746            mServiceInfo = serviceInfo;
747            mIntent = new Intent().setComponent(mComponentName);
748            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));
752        }
753
754        /**
755         * Binds to the accessibility service.
756         *
757         * @return True if binding is successful.
758         */
759        public boolean bind() {
760            if (mService == null) {
761                return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
762            }
763            return false;
764        }
765
766        /**
767         * Unbinds form the accessibility service and removes it from the data
768         * structures for service management.
769         *
770         * @return True if unbinding is successful.
771         */
772        public boolean unbind() {
773            if (mService != null) {
774                mService = null;
775                mContext.unbindService(this);
776                mComponentNameToServiceMap.remove(mComponentName);
777                mServices.remove(this);
778                updateStateOnDisabledService(this);
779                return true;
780            }
781            return false;
782        }
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) {
811                Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
812            }
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}
823