AccessibilityManagerService.java revision 63c04eeb00dd71380d5ebba701014793d8f9a2ea
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 android.Manifest;
20import android.accessibilityservice.AccessibilityService;
21import android.accessibilityservice.AccessibilityServiceInfo;
22import android.accessibilityservice.IAccessibilityServiceConnection;
23import android.accessibilityservice.IEventListener;
24import android.app.PendingIntent;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.ServiceConnection;
32import android.content.pm.PackageManager;
33import android.content.pm.ResolveInfo;
34import android.database.ContentObserver;
35import android.graphics.Rect;
36import android.net.Uri;
37import android.os.Binder;
38import android.os.Handler;
39import android.os.IBinder;
40import android.os.Message;
41import android.os.RemoteException;
42import android.os.ServiceManager;
43import android.os.SystemClock;
44import android.provider.Settings;
45import android.text.TextUtils;
46import android.text.TextUtils.SimpleStringSplitter;
47import android.util.Slog;
48import android.util.SparseArray;
49import android.view.IWindow;
50import android.view.View;
51import android.view.accessibility.AccessibilityEvent;
52import android.view.accessibility.AccessibilityNodeInfo;
53import android.view.accessibility.IAccessibilityInteractionConnection;
54import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
55import android.view.accessibility.IAccessibilityManager;
56import android.view.accessibility.IAccessibilityManagerClient;
57
58import com.android.internal.content.PackageMonitor;
59import com.android.internal.os.HandlerCaller;
60import com.android.internal.os.HandlerCaller.SomeArgs;
61import com.android.server.wm.WindowManagerService;
62
63import org.xmlpull.v1.XmlPullParserException;
64
65import java.io.IOException;
66import java.util.ArrayList;
67import java.util.Arrays;
68import java.util.HashMap;
69import java.util.HashSet;
70import java.util.Iterator;
71import java.util.List;
72import java.util.Map;
73import java.util.Set;
74import java.util.concurrent.atomic.AtomicInteger;
75
76/**
77 * This class is instantiated by the system as a system level service and can be
78 * accessed only by the system. The task of this service is to be a centralized
79 * event dispatch for {@link AccessibilityEvent}s generated across all processes
80 * on the device. Events are dispatched to {@link AccessibilityService}s.
81 *
82 * @hide
83 */
84public class AccessibilityManagerService extends IAccessibilityManager.Stub
85        implements HandlerCaller.Callback {
86
87    private static final boolean DEBUG = false;
88
89    private static final String LOG_TAG = "AccessibilityManagerService";
90
91    private static final String FUNCTION_REGISTER_EVENT_LISTENER =
92        "registerEventListener";
93
94    private static int sIdCounter = 0;
95
96    private static final int OWN_PROCESS_ID = android.os.Process.myPid();
97
98    private static final int DO_SET_SERVICE_INFO = 10;
99
100    private static int sNextWindowId;
101
102    final HandlerCaller mCaller;
103
104    final Context mContext;
105
106    final Object mLock = new Object();
107
108    final List<Service> mServices = new ArrayList<Service>();
109
110    final List<IAccessibilityManagerClient> mClients =
111        new ArrayList<IAccessibilityManagerClient>();
112
113    final Map<ComponentName, Service> mComponentNameToServiceMap = new HashMap<ComponentName, Service>();
114
115    private final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<AccessibilityServiceInfo>();
116
117    private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
118
119    private final SparseArray<IAccessibilityInteractionConnection> mWindowIdToInteractionConnectionMap =
120        new SparseArray<IAccessibilityInteractionConnection>();
121
122    private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>();
123
124    private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
125
126    private PackageManager mPackageManager;
127
128    private int mHandledFeedbackTypes = 0;
129
130    private boolean mIsEnabled;
131
132    private AccessibilityInputFilter mInputFilter;
133
134    private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>();
135
136    private boolean mHasInputFilter;
137
138    private final WindowManagerService mWindowManagerService;
139
140    private final SecurityPolicy mSecurityPolicy;
141
142    /**
143     * Handler for delayed event dispatch.
144     */
145    private Handler mHandler = new Handler() {
146
147        @Override
148        public void handleMessage(Message message) {
149            Service service = (Service) message.obj;
150            int eventType = message.arg1;
151
152            synchronized (mLock) {
153                notifyEventListenerLocked(service, eventType);
154            }
155        }
156    };
157
158    /**
159     * Creates a new instance.
160     *
161     * @param context A {@link Context} instance.
162     */
163    public AccessibilityManagerService(Context context) {
164        mContext = context;
165        mPackageManager = mContext.getPackageManager();
166        mCaller = new HandlerCaller(context, this);
167        mWindowManagerService = (WindowManagerService) ServiceManager.getService(
168                Context.WINDOW_SERVICE);
169        mSecurityPolicy = new SecurityPolicy();
170
171        registerPackageChangeAndBootCompletedBroadcastReceiver();
172        registerSettingsContentObservers();
173    }
174
175    /**
176     * Registers a {@link BroadcastReceiver} for the events of
177     * adding/changing/removing/restarting a package and boot completion.
178     */
179    private void registerPackageChangeAndBootCompletedBroadcastReceiver() {
180        Context context = mContext;
181
182        PackageMonitor monitor = new PackageMonitor() {
183            @Override
184            public void onSomePackagesChanged() {
185                synchronized (mLock) {
186                    populateAccessibilityServiceListLocked();
187                    manageServicesLocked();
188                }
189            }
190
191            @Override
192            public boolean onHandleForceStop(Intent intent, String[] packages,
193                    int uid, boolean doit) {
194                synchronized (mLock) {
195                    boolean changed = false;
196                    Iterator<ComponentName> it = mEnabledServices.iterator();
197                    while (it.hasNext()) {
198                        ComponentName comp = it.next();
199                        String compPkg = comp.getPackageName();
200                        for (String pkg : packages) {
201                            if (compPkg.equals(pkg)) {
202                                if (!doit) {
203                                    return true;
204                                }
205                                it.remove();
206                                changed = true;
207                            }
208                        }
209                    }
210                    if (changed) {
211                        it = mEnabledServices.iterator();
212                        StringBuilder str = new StringBuilder();
213                        while (it.hasNext()) {
214                            if (str.length() > 0) {
215                                str.append(':');
216                            }
217                            str.append(it.next().flattenToShortString());
218                        }
219                        Settings.Secure.putString(mContext.getContentResolver(),
220                                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
221                                str.toString());
222                        manageServicesLocked();
223                    }
224                    return false;
225                }
226            }
227
228            @Override
229            public void onReceive(Context context, Intent intent) {
230                if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
231                    synchronized (mLock) {
232                        populateAccessibilityServiceListLocked();
233                        // get the accessibility enabled setting on boot
234                        mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
235                                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
236
237                        // if accessibility is enabled inform our clients we are on
238                        if (mIsEnabled) {
239                            updateClientsLocked();
240                        }
241
242                        manageServicesLocked();
243                    }
244
245                    return;
246                }
247
248                super.onReceive(context, intent);
249            }
250        };
251
252        // package changes
253        monitor.register(context, true);
254
255        // boot completed
256        IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
257        mContext.registerReceiver(monitor, bootFiler);
258    }
259
260    /**
261     * {@link ContentObserver}s for {@link Settings.Secure#ACCESSIBILITY_ENABLED}
262     * and {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} settings.
263     */
264    private void registerSettingsContentObservers() {
265        ContentResolver contentResolver = mContext.getContentResolver();
266
267        Uri enabledUri = Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_ENABLED);
268        contentResolver.registerContentObserver(enabledUri, false,
269            new ContentObserver(new Handler()) {
270                @Override
271                public void onChange(boolean selfChange) {
272                    super.onChange(selfChange);
273
274                    synchronized (mLock) {
275                        mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
276                                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
277                        if (mIsEnabled) {
278                            manageServicesLocked();
279                        } else {
280                            unbindAllServicesLocked();
281                        }
282                        updateClientsLocked();
283                    }
284                }
285            });
286
287        Uri providersUri =
288            Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
289        contentResolver.registerContentObserver(providersUri, false,
290            new ContentObserver(new Handler()) {
291                @Override
292                public void onChange(boolean selfChange) {
293                    super.onChange(selfChange);
294
295                    synchronized (mLock) {
296                        manageServicesLocked();
297                    }
298                }
299            });
300    }
301
302    public boolean addClient(IAccessibilityManagerClient client) throws RemoteException {
303        synchronized (mLock) {
304            final IAccessibilityManagerClient addedClient = client;
305            mClients.add(addedClient);
306            // Clients are registered all the time until their process is
307            // killed, therefore we do not have a corresponding unlinkToDeath.
308            client.asBinder().linkToDeath(new DeathRecipient() {
309                public void binderDied() {
310                    synchronized (mLock) {
311                        mClients.remove(addedClient);
312                    }
313                }
314            }, 0);
315            return mIsEnabled;
316        }
317    }
318
319    public boolean sendAccessibilityEvent(AccessibilityEvent event) {
320        synchronized (mLock) {
321            if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) {
322                mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
323                notifyAccessibilityServicesDelayedLocked(event, false);
324                notifyAccessibilityServicesDelayedLocked(event, true);
325            }
326        }
327        event.recycle();
328        mHandledFeedbackTypes = 0;
329        return (OWN_PROCESS_ID != Binder.getCallingPid());
330    }
331
332    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
333        synchronized (mLock) {
334            return mInstalledServices;
335        }
336    }
337
338    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
339        List<AccessibilityServiceInfo> result = mEnabledServicesForFeedbackTempList;
340        result.clear();
341        List<Service> services = mServices;
342        synchronized (mLock) {
343            while (feedbackType != 0) {
344                final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType));
345                feedbackType &= ~feedbackTypeBit;
346                final int serviceCount = services.size();
347                for (int i = 0; i < serviceCount; i++) {
348                    Service service = services.get(i);
349                    if ((service.mFeedbackType & feedbackTypeBit) != 0) {
350                        result.add(service.mAccessibilityServiceInfo);
351                    }
352                }
353            }
354        }
355        return result;
356    }
357
358    public void interrupt() {
359        synchronized (mLock) {
360            for (int i = 0, count = mServices.size(); i < count; i++) {
361                Service service = mServices.get(i);
362                try {
363                    service.mServiceInterface.onInterrupt();
364                } catch (RemoteException re) {
365                    Slog.e(LOG_TAG, "Error during sending interrupt request to "
366                        + service.mService, re);
367                }
368            }
369        }
370    }
371
372    public void executeMessage(Message message) {
373        switch (message.what) {
374            case DO_SET_SERVICE_INFO: {
375                SomeArgs arguments = ((SomeArgs) message.obj);
376
377                AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1;
378                Service service = (Service) arguments.arg2;
379
380                synchronized (mLock) {
381                    // If the XML manifest had data to configure the service its info
382                    // should be already set. In such a case update only the dynamically
383                    // configurable properties.
384                    AccessibilityServiceInfo oldInfo = service.mAccessibilityServiceInfo;
385                    if (oldInfo != null) {
386                        oldInfo.updateDynamicallyConfigurableProperties(info);
387                        service.setDynamicallyConfigurableProperties(oldInfo);
388                    } else {
389                        service.setDynamicallyConfigurableProperties(info);
390                    }
391                }
392            } return;
393            default:
394                Slog.w(LOG_TAG, "Unknown message type: " + message.what);
395        }
396    }
397
398    public int addAccessibilityInteractionConnection(IWindow windowToken,
399            IAccessibilityInteractionConnection connection) throws RemoteException {
400        synchronized (mLock) {
401            final IWindow addedWindowToken = windowToken;
402            final int windowId = sNextWindowId++;
403            connection.asBinder().linkToDeath(new DeathRecipient() {
404                public void binderDied() {
405                    synchronized (mLock) {
406                        removeAccessibilityInteractionConnection(addedWindowToken);
407                    }
408                }
409            }, 0);
410            mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder());
411            mWindowIdToInteractionConnectionMap.put(windowId, connection);
412            if (DEBUG) {
413                Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId);
414            }
415            return windowId;
416        }
417    }
418
419    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
420        synchronized (mLock) {
421            final int count = mWindowIdToWindowTokenMap.size();
422            for (int i = 0; i < count; i++) {
423                if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) {
424                    final int windowId = mWindowIdToWindowTokenMap.keyAt(i);
425                    mWindowIdToWindowTokenMap.remove(windowId);
426                    mWindowIdToInteractionConnectionMap.remove(windowId);
427                    if (DEBUG) {
428                        Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
429                    }
430                    return;
431                }
432            }
433        }
434    }
435
436    public IAccessibilityServiceConnection registerEventListener(IEventListener listener) {
437        mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
438                FUNCTION_REGISTER_EVENT_LISTENER);
439        ComponentName componentName = new ComponentName("foo.bar",
440                "AutomationAccessibilityService");
441        synchronized (mLock) {
442            Service oldService = mComponentNameToServiceMap.get(componentName);
443            if (oldService != null) {
444                tryRemoveServiceLocked(oldService);
445            }
446            // This API is intended for testing so enable accessibility to make
447            // sure clients can start poking with the window content.
448            Settings.Secure.putInt(mContext.getContentResolver(),
449                    Settings.Secure.ACCESSIBILITY_ENABLED, 1);
450            // Also disable all accessibility services to avoid interference
451            // with the tests.
452            Settings.Secure.putString(mContext.getContentResolver(),
453                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
454        }
455        AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
456        accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
457        accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
458        Service service = new Service(componentName, accessibilityServiceInfo, true);
459        service.onServiceConnected(componentName, listener.asBinder());
460        return service;
461    }
462
463    /**
464     * Populates the cached list of installed {@link AccessibilityService}s.
465     */
466    private void populateAccessibilityServiceListLocked() {
467        mInstalledServices.clear();
468
469        List<ResolveInfo> installedServices = mPackageManager.queryIntentServices(
470                new Intent(AccessibilityService.SERVICE_INTERFACE),
471                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
472
473        for (int i = 0, count = installedServices.size(); i < count; i++) {
474            ResolveInfo resolveInfo = installedServices.get(i);
475            AccessibilityServiceInfo accessibilityServiceInfo;
476            try {
477                accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
478                mInstalledServices.add(accessibilityServiceInfo);
479            } catch (XmlPullParserException xppe) {
480                Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
481            } catch (IOException ioe) {
482                Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", ioe);
483            }
484        }
485    }
486
487    /**
488     * Performs {@link AccessibilityService}s delayed notification. The delay is configurable
489     * and denotes the period after the last event before notifying the service.
490     *
491     * @param event The event.
492     * @param isDefault True to notify default listeners, not default services.
493     */
494    private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
495            boolean isDefault) {
496        try {
497            for (int i = 0, count = mServices.size(); i < count; i++) {
498                Service service = mServices.get(i);
499
500                if (service.mIsDefault == isDefault) {
501                    if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
502                        mHandledFeedbackTypes |= service.mFeedbackType;
503                        notifyAccessibilityServiceDelayedLocked(service, event);
504                    }
505                }
506            }
507        } catch (IndexOutOfBoundsException oobe) {
508            // An out of bounds exception can happen if services are going away
509            // as the for loop is running. If that happens, just bail because
510            // there are no more services to notify.
511            return;
512        }
513    }
514
515    /**
516     * Performs an {@link AccessibilityService} delayed notification. The delay is configurable
517     * and denotes the period after the last event before notifying the service.
518     *
519     * @param service The service.
520     * @param event The event.
521     */
522    private void notifyAccessibilityServiceDelayedLocked(Service service,
523            AccessibilityEvent event) {
524        synchronized (mLock) {
525            final int eventType = event.getEventType();
526            // Make a copy since during dispatch it is possible the event to
527            // be modified to remove its source if the receiving service does
528            // not have permission to access the window content.
529            AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
530            AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
531            service.mPendingEvents.put(eventType, newEvent);
532
533            final int what = eventType | (service.mId << 16);
534            if (oldEvent != null) {
535                mHandler.removeMessages(what);
536                oldEvent.recycle();
537            }
538
539            Message message = mHandler.obtainMessage(what, service);
540            message.arg1 = eventType;
541            mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
542        }
543    }
544
545    /**
546     * Notifies a service for a scheduled event given the event type.
547     *
548     * @param service The service.
549     * @param eventType The type of the event to dispatch.
550     */
551    private void notifyEventListenerLocked(Service service, int eventType) {
552        IEventListener listener = service.mServiceInterface;
553        AccessibilityEvent event = service.mPendingEvents.get(eventType);
554
555        // Check for null here because there is a concurrent scenario in which this
556        // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
557        // which posts a message for dispatching an event. 2) The message is pulled
558        // from the queue by the handler on the service thread and the latter is
559        // just about to acquire the lock and call this method. 3) Now another binder
560        // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
561        // so the service thread waits for the lock; 4) The binder thread replaces
562        // the event with a more recent one (assume the same event type) and posts a
563        // dispatch request releasing the lock. 5) Now the main thread is unblocked and
564        // dispatches the event which is removed from the pending ones. 6) And ... now
565        // the service thread handles the last message posted by the last binder call
566        // but the event is already dispatched and hence looking it up in the pending
567        // ones yields null. This check is much simpler that keeping count for each
568        // event type of each service to catch such a scenario since only one message
569        // is processed at a time.
570        if (event == null) {
571            return;
572        }
573
574        service.mPendingEvents.remove(eventType);
575        try {
576            if (mSecurityPolicy.canRetrieveWindowContent(service)) {
577                event.setConnection(service);
578            } else {
579                event.setSource(null);
580            }
581            event.setSealed(true);
582            listener.onAccessibilityEvent(event);
583            event.recycle();
584            if (DEBUG) {
585                Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
586            }
587        } catch (RemoteException re) {
588            Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
589        }
590    }
591
592    /**
593     * Adds a service.
594     *
595     * @param service The service to add.
596     */
597    private void tryAddServiceLocked(Service service) {
598        try {
599            if (mServices.contains(service) || !service.isConfigured()) {
600                return;
601            }
602            service.linkToOwnDeath();
603            mServices.add(service);
604            mComponentNameToServiceMap.put(service.mComponentName, service);
605            updateInputFilterLocked();
606        } catch (RemoteException e) {
607            /* do nothing */
608        }
609    }
610
611    /**
612     * Removes a service.
613     *
614     * @param service The service.
615     * @return True if the service was removed, false otherwise.
616     */
617    private boolean tryRemoveServiceLocked(Service service) {
618        final boolean removed = mServices.remove(service);
619        if (!removed) {
620            return false;
621        }
622        mComponentNameToServiceMap.remove(service.mComponentName);
623        mHandler.removeMessages(service.mId);
624        service.unlinkToOwnDeath();
625        updateInputFilterLocked();
626        return removed;
627    }
628
629    /**
630     * Determines if given event can be dispatched to a service based on the package of the
631     * event source and already notified services for that event type. Specifically, a
632     * service is notified if it is interested in events from the package and no other service
633     * providing the same feedback type has been notified. Exception are services the
634     * provide generic feedback (feedback type left as a safety net for unforeseen feedback
635     * types) which are always notified.
636     *
637     * @param service The potential receiver.
638     * @param event The event.
639     * @param handledFeedbackTypes The feedback types for which services have been notified.
640     * @return True if the listener should be notified, false otherwise.
641     */
642    private boolean canDispathEventLocked(Service service, AccessibilityEvent event,
643            int handledFeedbackTypes) {
644
645        if (!service.isConfigured()) {
646            return false;
647        }
648
649        int eventType = event.getEventType();
650        if ((service.mEventTypes & eventType) != eventType) {
651            return false;
652        }
653
654        Set<String> packageNames = service.mPackageNames;
655        CharSequence packageName = event.getPackageName();
656
657        if (packageNames.isEmpty() || packageNames.contains(packageName)) {
658            int feedbackType = service.mFeedbackType;
659            if ((handledFeedbackTypes & feedbackType) != feedbackType
660                    || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
661                return true;
662            }
663        }
664
665        return false;
666    }
667
668    /**
669     * Manages services by starting enabled ones and stopping disabled ones.
670     */
671    private void manageServicesLocked() {
672        populateEnabledServicesLocked(mEnabledServices);
673        updateServicesStateLocked(mInstalledServices, mEnabledServices);
674    }
675
676    /**
677     * Unbinds all bound services.
678     */
679    private void unbindAllServicesLocked() {
680        List<Service> services = mServices;
681
682        for (int i = 0, count = services.size(); i < count; i++) {
683            Service service = services.get(i);
684            if (service.unbind()) {
685                i--;
686                count--;
687            }
688        }
689    }
690
691    /**
692     * Populates a list with the {@link ComponentName}s of all enabled
693     * {@link AccessibilityService}s.
694     *
695     * @param enabledServices The list.
696     */
697    private void populateEnabledServicesLocked(Set<ComponentName> enabledServices) {
698        enabledServices.clear();
699
700        String servicesValue = Settings.Secure.getString(mContext.getContentResolver(),
701                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
702
703        if (servicesValue != null) {
704            TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
705            splitter.setString(servicesValue);
706            while (splitter.hasNext()) {
707                String str = splitter.next();
708                if (str == null || str.length() <= 0) {
709                    continue;
710                }
711                ComponentName enabledService = ComponentName.unflattenFromString(str);
712                if (enabledService != null) {
713                    enabledServices.add(enabledService);
714                }
715            }
716        }
717    }
718
719    /**
720     * Updates the state of each service by starting (or keeping running) enabled ones and
721     * stopping the rest.
722     *
723     * @param installedServices All installed {@link AccessibilityService}s.
724     * @param enabledServices The {@link ComponentName}s of the enabled services.
725     */
726    private void updateServicesStateLocked(List<AccessibilityServiceInfo> installedServices,
727            Set<ComponentName> enabledServices) {
728
729        Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap;
730        boolean isEnabled = mIsEnabled;
731
732        for (int i = 0, count = installedServices.size(); i < count; i++) {
733            AccessibilityServiceInfo installedService = installedServices.get(i);
734            ComponentName componentName = ComponentName.unflattenFromString(
735                    installedService.getId());
736            Service service = componentNameToServiceMap.get(componentName);
737
738            if (isEnabled) {
739                if (enabledServices.contains(componentName)) {
740                    if (service == null) {
741                        service = new Service(componentName, installedService, false);
742                    }
743                    service.bind();
744                } else if (!enabledServices.contains(componentName)) {
745                    if (service != null) {
746                        service.unbind();
747                    }
748                }
749            } else {
750                if (service != null) {
751                    service.unbind();
752                }
753            }
754        }
755    }
756
757    /**
758     * Updates the state of {@link android.view.accessibility.AccessibilityManager} clients.
759     */
760    private void updateClientsLocked() {
761        for (int i = 0, count = mClients.size(); i < count; i++) {
762            try {
763                mClients.get(i).setEnabled(mIsEnabled);
764            } catch (RemoteException re) {
765                mClients.remove(i);
766                count--;
767                i--;
768            }
769        }
770    }
771
772    /**
773     * Updates the input filter state. The filter is enabled if accessibility
774     * is enabled and there is at least one accessibility service providing
775     * spoken feedback.
776     */
777    private void updateInputFilterLocked() {
778        if (mIsEnabled) {
779            final boolean hasSpokenFeedbackServices = !getEnabledAccessibilityServiceList(
780                    AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
781            if (hasSpokenFeedbackServices) {
782                if (mHasInputFilter) {
783                    return;
784                }
785                if (mInputFilter == null) {
786                    mInputFilter = new AccessibilityInputFilter(mContext);
787                }
788                mWindowManagerService.setInputFilter(mInputFilter);
789                mHasInputFilter = true;
790                return;
791            }
792        }
793        if (mHasInputFilter) {
794            mWindowManagerService.setInputFilter(null);
795            mHasInputFilter = false;
796        }
797    }
798
799    /**
800     * This class represents an accessibility service. It stores all per service
801     * data required for the service management, provides API for starting/stopping the
802     * service and is responsible for adding/removing the service in the data structures
803     * for service management. The class also exposes configuration interface that is
804     * passed to the service it represents as soon it is bound. It also serves as the
805     * connection for the service.
806     */
807    class Service extends IAccessibilityServiceConnection.Stub
808            implements ServiceConnection, DeathRecipient {
809        int mId = 0;
810
811        AccessibilityServiceInfo mAccessibilityServiceInfo;
812
813        IBinder mService;
814
815        IEventListener mServiceInterface;
816
817        int mEventTypes;
818
819        int mFeedbackType;
820
821        Set<String> mPackageNames = new HashSet<String>();
822
823        boolean mIsDefault;
824
825        long mNotificationTimeout;
826
827        ComponentName mComponentName;
828
829        Intent mIntent;
830
831        boolean mCanRetrieveScreenContent;
832
833        boolean mIsAutomation;
834
835        final Callback mCallback = new Callback();
836
837        final AtomicInteger mInteractionIdCounter = new AtomicInteger();
838
839        final Rect mTempBounds = new Rect();
840
841        // the events pending events to be dispatched to this service
842        final SparseArray<AccessibilityEvent> mPendingEvents =
843            new SparseArray<AccessibilityEvent>();
844
845        public Service(ComponentName componentName,
846                AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) {
847            mId = sIdCounter++;
848            mComponentName = componentName;
849            mAccessibilityServiceInfo = accessibilityServiceInfo;
850            mIsAutomation = isAutomation;
851            if (!isAutomation) {
852                mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent();
853                mIntent = new Intent().setComponent(mComponentName);
854                mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
855                        com.android.internal.R.string.accessibility_binding_label);
856                mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
857                        mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
858            } else {
859                mCanRetrieveScreenContent = true;
860            }
861            setDynamicallyConfigurableProperties(accessibilityServiceInfo);
862        }
863
864        public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
865            mEventTypes = info.eventTypes;
866            mFeedbackType = info.feedbackType;
867            String[] packageNames = info.packageNames;
868            if (packageNames != null) {
869                mPackageNames.addAll(Arrays.asList(packageNames));
870            }
871            mNotificationTimeout = info.notificationTimeout;
872            mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0;
873
874            synchronized (mLock) {
875                tryAddServiceLocked(this);
876            }
877        }
878
879        /**
880         * Binds to the accessibility service.
881         *
882         * @return True if binding is successful.
883         */
884        public boolean bind() {
885            if (!mIsAutomation && mService == null) {
886                return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
887            }
888            return false;
889        }
890
891        /**
892         * Unbinds form the accessibility service and removes it from the data
893         * structures for service management.
894         *
895         * @return True if unbinding is successful.
896         */
897        public boolean unbind() {
898            if (mService != null) {
899                synchronized (mLock) {
900                    tryRemoveServiceLocked(this);
901                }
902                if (!mIsAutomation) {
903                    mContext.unbindService(this);
904                }
905                mService = null;
906                return true;
907            }
908            return false;
909        }
910
911        /**
912         * Returns if the service is configured i.e. at least event types of interest
913         * and feedback type must be set.
914         *
915         * @return True if the service is configured, false otherwise.
916         */
917        public boolean isConfigured() {
918            return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
919        }
920
921        public void setServiceInfo(AccessibilityServiceInfo info) {
922            mCaller.obtainMessageOO(DO_SET_SERVICE_INFO, info, this).sendToTarget();
923        }
924
925        public void onServiceConnected(ComponentName componentName, IBinder service) {
926            mService = service;
927            mServiceInterface = IEventListener.Stub.asInterface(service);
928            try {
929                mServiceInterface.setConnection(this);
930                synchronized (mLock) {
931                    tryAddServiceLocked(this);
932                }
933            } catch (RemoteException re) {
934                Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
935            }
936        }
937
938        public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
939            IAccessibilityInteractionConnection connection = null;
940            synchronized (mLock) {
941                final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
942                if (!permissionGranted) {
943                    return null;
944                } else {
945                    connection = getConnectionToRetrievalAllowingWindowLocked();
946                    if (connection == null) {
947                        if (DEBUG) {
948                            Slog.e(LOG_TAG, "No interaction connection to a retrieve "
949                                    + "allowing window.");
950                        }
951                        return null;
952                    }
953                }
954            }
955            final long identityToken = Binder.clearCallingIdentity();
956            try {
957                final int interactionId = mInteractionIdCounter.getAndIncrement();
958                connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, mCallback);
959                AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear(
960                        interactionId);
961                if (info != null) {
962                    applyCompatibilityScaleIfNeeded(info);
963                    info.setConnection(this);
964                    info.setSealed(true);
965                }
966                return info;
967            } catch (RemoteException re) {
968                if (DEBUG) {
969                    Slog.e(LOG_TAG, "Error finding node.");
970                }
971            } finally {
972                Binder.restoreCallingIdentity(identityToken);
973            }
974            return null;
975        }
976
977        public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(
978                String text) {
979            return findAccessibilityNodeInfosByViewText(text,
980                    mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID);
981        }
982
983        public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text,
984                int accessibilityWindowId, int accessibilityViewId) {
985            IAccessibilityInteractionConnection connection = null;
986            synchronized (mLock) {
987                final boolean permissionGranted =
988                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
989                if (!permissionGranted) {
990                    return null;
991                } else {
992                    connection = getConnectionToRetrievalAllowingWindowLocked();
993                    if (connection == null) {
994                        if (DEBUG) {
995                            Slog.e(LOG_TAG, "No interaction connection to focused window.");
996                        }
997                        return null;
998                    }
999                }
1000            }
1001            final long identityToken = Binder.clearCallingIdentity();
1002            try {
1003                final int interactionId = mInteractionIdCounter.getAndIncrement();
1004                connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId,
1005                        interactionId, mCallback);
1006                List<AccessibilityNodeInfo> infos =
1007                    mCallback.getFindAccessibilityNodeInfosResultAndClear(interactionId);
1008                if (infos != null) {
1009                    final int infoCount = infos.size();
1010                    for (int i = 0; i < infoCount; i++) {
1011                        AccessibilityNodeInfo info = infos.get(i);
1012                        applyCompatibilityScaleIfNeeded(info);
1013                        info.setConnection(this);
1014                        info.setSealed(true);
1015                    }
1016                }
1017                return infos;
1018            } catch (RemoteException re) {
1019                if (DEBUG) {
1020                    Slog.e(LOG_TAG, "Error finding node.");
1021                }
1022            } finally {
1023                Binder.restoreCallingIdentity(identityToken);
1024            }
1025            return null;
1026        }
1027
1028        public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
1029                int accessibilityWindowId, int accessibilityViewId) {
1030            IAccessibilityInteractionConnection connection = null;
1031            synchronized (mLock) {
1032                final boolean permissionGranted =
1033                    mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId);
1034                if (!permissionGranted) {
1035                    return null;
1036                } else {
1037                    connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
1038                    if (connection == null) {
1039                        if (DEBUG) {
1040                            Slog.e(LOG_TAG, "No interaction connection to window: "
1041                                    + accessibilityWindowId);
1042                        }
1043                        return null;
1044                    }
1045                }
1046            }
1047            final long identityToken = Binder.clearCallingIdentity();
1048            try {
1049                final int interactionId = mInteractionIdCounter.getAndIncrement();
1050                connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityViewId,
1051                        interactionId, mCallback);
1052                AccessibilityNodeInfo info =
1053                     mCallback.getFindAccessibilityNodeInfoResultAndClear(interactionId);
1054                if (info != null) {
1055                    applyCompatibilityScaleIfNeeded(info);
1056                    info.setConnection(this);
1057                    info.setSealed(true);
1058                }
1059                return info;
1060            } catch (RemoteException re) {
1061                if (DEBUG) {
1062                    Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: "
1063                            + accessibilityViewId);
1064                }
1065            } finally {
1066                Binder.restoreCallingIdentity(identityToken);
1067            }
1068            return null;
1069        }
1070
1071        public boolean performAccessibilityAction(int accessibilityWindowId,
1072                int accessibilityViewId, int action) {
1073            IAccessibilityInteractionConnection connection = null;
1074            synchronized (mLock) {
1075                final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this,
1076                        accessibilityWindowId, action);
1077                if (!permissionGranted) {
1078                    return false;
1079                } else {
1080                    connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId);
1081                    if (connection == null) {
1082                        if (DEBUG) {
1083                            Slog.e(LOG_TAG, "No interaction connection to window: "
1084                                    + accessibilityWindowId);
1085                        }
1086                        return false;
1087                    }
1088                }
1089            }
1090            final long identityToken = Binder.clearCallingIdentity();
1091            try {
1092                final int interactionId = mInteractionIdCounter.getAndIncrement();
1093                connection.performAccessibilityAction(accessibilityViewId, action, interactionId,
1094                        mCallback);
1095                return mCallback.getPerformAccessibilityActionResult(interactionId);
1096            } catch (RemoteException re) {
1097                if (DEBUG) {
1098                    Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: "
1099                            + accessibilityViewId);
1100                }
1101            } finally {
1102                Binder.restoreCallingIdentity(identityToken);
1103            }
1104            return false;
1105        }
1106
1107        public void onServiceDisconnected(ComponentName componentName) {
1108            /* do nothing - #binderDied takes care */
1109        }
1110
1111        public void linkToOwnDeath() throws RemoteException {
1112            mService.linkToDeath(this, 0);
1113        }
1114
1115        public void unlinkToOwnDeath() {
1116            mService.unlinkToDeath(this, 0);
1117        }
1118
1119        public void binderDied() {
1120            synchronized (mLock) {
1121                tryRemoveServiceLocked(this);
1122            }
1123        }
1124
1125        private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() {
1126            final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked();
1127            if (DEBUG) {
1128                Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
1129            }
1130            return mWindowIdToInteractionConnectionMap.get(windowId);
1131        }
1132
1133        private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info) {
1134            IBinder windowToken = mWindowIdToWindowTokenMap.get(info.getWindowId());
1135            final float scale = mWindowManagerService.getWindowCompatibilityScale(windowToken);
1136
1137            if (scale == 1.0f) {
1138                return;
1139            }
1140
1141            Rect bounds = mTempBounds;
1142            info.getBoundsInParent(bounds);
1143            bounds.scale(scale);
1144            info.setBoundsInParent(bounds);
1145
1146            info.getBoundsInScreen(bounds);
1147            bounds.scale(scale);
1148            info.setBoundsInScreen(bounds);
1149        }
1150    }
1151
1152    final class SecurityPolicy {
1153        private static final int VALID_ACTIONS = AccessibilityNodeInfo.ACTION_FOCUS
1154            | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS | AccessibilityNodeInfo.ACTION_SELECT
1155            | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
1156
1157        private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
1158            AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED
1159            | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
1160            | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
1161            | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED
1162            | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
1163            | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
1164            | AccessibilityEvent.TYPE_VIEW_SCROLLED;
1165
1166        private int mRetrievalAlowingWindowId;
1167
1168        private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) {
1169            // Send window changed event only for the retrieval allowing window.
1170            return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
1171                    || event.getWindowId() == mRetrievalAlowingWindowId);
1172        }
1173
1174        public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) {
1175            final int windowId = event.getWindowId();
1176            final int eventType = event.getEventType();
1177            if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) != 0) {
1178                mRetrievalAlowingWindowId = windowId;
1179            } else {
1180                event.setSource(null);
1181            }
1182        }
1183
1184        public int getRetrievalAllowingWindowLocked() {
1185            return mRetrievalAlowingWindowId;
1186        }
1187
1188        public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) {
1189            return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId);
1190        }
1191
1192        public boolean canPerformActionLocked(Service service, int windowId, int action) {
1193            return canRetrieveWindowContent(service)
1194                && isRetrievalAllowingWindow(windowId)
1195                && isActionPermitted(action);
1196        }
1197
1198        public boolean canRetrieveWindowContent(Service service) {
1199            return service.mCanRetrieveScreenContent;
1200        }
1201
1202        private boolean isRetrievalAllowingWindow(int windowId) {
1203            return (mRetrievalAlowingWindowId == windowId);
1204        }
1205
1206        private boolean isActionPermitted(int action) {
1207             return (VALID_ACTIONS & action) != 0;
1208        }
1209
1210        private void enforceCallingPermission(String permission, String function) {
1211            if (OWN_PROCESS_ID == Binder.getCallingPid()) {
1212                return;
1213            }
1214            final int permissionStatus = mContext.checkCallingPermission(permission);
1215            if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
1216                throw new SecurityException("You do not have " + permission
1217                        + " required to call " + function);
1218            }
1219        }
1220    }
1221
1222    final class Callback extends IAccessibilityInteractionConnectionCallback.Stub {
1223        private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
1224
1225        private int mInteractionId = -1;
1226        private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
1227        private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
1228        private boolean mPerformAccessibilityActionResult;
1229
1230        public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
1231                int interactionId) {
1232            synchronized (mLock) {
1233                if (interactionId > mInteractionId) {
1234                    mFindAccessibilityNodeInfoResult = info;
1235                    mInteractionId = interactionId;
1236                }
1237                mLock.notifyAll();
1238            }
1239        }
1240
1241        public AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
1242            synchronized (mLock) {
1243                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
1244                AccessibilityNodeInfo result = mFindAccessibilityNodeInfoResult;
1245                clearLocked();
1246                return result;
1247            }
1248        }
1249
1250        public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
1251                int interactionId) {
1252            synchronized (mLock) {
1253                if (interactionId > mInteractionId) {
1254                    mFindAccessibilityNodeInfosResult = infos;
1255                    mInteractionId = interactionId;
1256                }
1257                mLock.notifyAll();
1258            }
1259        }
1260
1261        public List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
1262                int interactionId) {
1263            synchronized (mLock) {
1264                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
1265                List<AccessibilityNodeInfo> result = mFindAccessibilityNodeInfosResult;
1266                clearLocked();
1267                return result;
1268            }
1269        }
1270
1271        public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
1272            synchronized (mLock) {
1273                if (interactionId > mInteractionId) {
1274                    mPerformAccessibilityActionResult = succeeded;
1275                    mInteractionId = interactionId;
1276                }
1277                mLock.notifyAll();
1278            }
1279        }
1280
1281        public boolean getPerformAccessibilityActionResult(int interactionId) {
1282            synchronized (mLock) {
1283                waitForResultTimedLocked(TIMEOUT_INTERACTION_MILLIS, interactionId);
1284                final boolean result = mPerformAccessibilityActionResult;
1285                clearLocked();
1286                return result;
1287            }
1288        }
1289
1290        public void clearLocked() {
1291            mInteractionId = -1;
1292            mFindAccessibilityNodeInfoResult = null;
1293            mFindAccessibilityNodeInfosResult = null;
1294            mPerformAccessibilityActionResult = false;
1295        }
1296
1297        private void waitForResultTimedLocked(long waitTimeMillis, int interactionId) {
1298            final long startTimeMillis = SystemClock.uptimeMillis();
1299            while (true) {
1300                try {
1301                    if (mInteractionId == interactionId) {
1302                        return;
1303                    }
1304                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
1305                    waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
1306                    if (waitTimeMillis <= 0) {
1307                        return;
1308                    }
1309                    mLock.wait(waitTimeMillis);
1310                } catch (InterruptedException ie) {
1311                    /* ignore */
1312                }
1313            }
1314        }
1315    }
1316}
1317