diff options
author | svetoslavganov <svetoslavganov@google.com> | 2009-05-14 22:28:01 -0700 |
---|---|---|
committer | svetoslavganov <svetoslavganov@google.com> | 2009-05-14 23:47:05 -0700 |
commit | 75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec (patch) | |
tree | 84e1843368037d24f83749d152f818d537267bfa /services | |
parent | 669ec3a6e47248fee0a3a0f4877b46875eb42140 (diff) | |
download | frameworks_base-75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec.zip frameworks_base-75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec.tar.gz frameworks_base-75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec.tar.bz2 |
Accessibility feature - framework changes (replacing 698, 699, 700, 701 and merging with the latest Donut)
Diffstat (limited to 'services')
3 files changed, 709 insertions, 10 deletions
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java new file mode 100644 index 0000000..c205fc0 --- /dev/null +++ b/services/java/com/android/server/AccessibilityManagerService.java @@ -0,0 +1,668 @@ +/* + ** Copyright 2009, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +package com.android.server; + +import static android.util.Config.LOGV; + +import com.android.internal.os.HandlerCaller; +import com.android.internal.os.HandlerCaller.SomeArgs; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.IAccessibilityServiceConnection; +import android.accessibilityservice.IEventListener; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.util.Log; +import android.util.SparseArray; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This class is instantiated by the system as a system level service and can be + * accessed only by the system. The task of this service is to be a centralized + * event dispatch for {@link AccessibilityEvent}s generated across all processes + * on the device. Events are dispatched to {@link AccessibilityService}s. + * + * @hide + */ +public class AccessibilityManagerService extends IAccessibilityManager.Stub + implements HandlerCaller.Callback { + + private static final String LOG_TAG = "AccessibilityManagerService"; + + private static int sIdCounter = 0; + + private static final int OWN_PROCESS_ID = android.os.Process.myPid(); + + private static final int DO_SET_SERVICE_INFO = 10; + + final HandlerCaller mCaller; + + final Context mContext; + + final Object mLock = new Object(); + + final List<Service> mServices = new ArrayList<Service>(); + + final List<IAccessibilityManagerClient> mClients = + new ArrayList<IAccessibilityManagerClient>(); + + final Map<ComponentName, Service> mComponentNameToServiceMap = + new HashMap<ComponentName, Service>(); + + private final List<ServiceInfo> mInstalledServices = new ArrayList<ServiceInfo>(); + + private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); + + private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':'); + + private PackageManager mPackageManager; + + private int mHandledFeedbackTypes = 0; + + private boolean mIsEnabled; + + /** + * Handler for delayed event dispatch. + */ + private Handler mHandler = new Handler() { + + @Override + public void handleMessage(Message message) { + Service service = (Service) message.obj; + int eventType = message.arg1; + + synchronized (mLock) { + notifyEventListenerLocked(service, eventType); + AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType); + service.mPendingEvents.remove(eventType); + tryRecycleLocked(oldEvent); + } + } + }; + + /** + * Creates a new instance. + * + * @param context A {@link Context} instance. + */ + AccessibilityManagerService(Context context) { + mContext = context; + mPackageManager = mContext.getPackageManager(); + mCaller = new HandlerCaller(context, this); + + registerPackageChangeAndBootCompletedBroadcastReceiver(); + registerSettingsContentObservers(); + + synchronized (mLock) { + populateAccessibilityServiceListLocked(); + } + } + + /** + * Registers a {@link BroadcastReceiver} for the events of + * adding/changing/removing/restarting a package and boot completion. + */ + private void registerPackageChangeAndBootCompletedBroadcastReceiver() { + Context context = mContext; + + BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + populateAccessibilityServiceListLocked(); + manageServicesLocked(); + + if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) { + mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; + updateClientsLocked(); + } + } + } + }; + + // package changes + IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + packageFilter.addDataScheme("package"); + context.registerReceiver(broadcastReceiver, packageFilter); + + // boot completed + IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); + mContext.registerReceiver(broadcastReceiver, bootFiler); + } + + /** + * {@link ContentObserver}s for {@link Settings.Secure#ACCESSIBILITY_ENABLED} + * and {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} settings. + */ + private void registerSettingsContentObservers() { + ContentResolver contentResolver = mContext.getContentResolver(); + + Uri enabledUri = Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_ENABLED); + contentResolver.registerContentObserver(enabledUri, false, + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; + + synchronized (mLock) { + if (mIsEnabled) { + manageServicesLocked(); + } else { + unbindAllServicesLocked(); + } + updateClientsLocked(); + } + } + }); + + Uri providersUri = + Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + contentResolver.registerContentObserver(providersUri, false, + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + synchronized (mLock) { + manageServicesLocked(); + } + } + }); + } + + public void addClient(IAccessibilityManagerClient client) { + synchronized (mLock) { + try { + client.setEnabled(mIsEnabled); + mClients.add(client); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re); + } + } + } + + public boolean sendAccessibilityEvent(AccessibilityEvent event) { + synchronized (mLock) { + notifyAccessibilityServicesDelayedLocked(event, false); + notifyAccessibilityServicesDelayedLocked(event, true); + } + // event not scheduled for dispatch => recycle + if (mHandledFeedbackTypes == 0) { + event.recycle(); + } else { + mHandledFeedbackTypes = 0; + } + + return (OWN_PROCESS_ID != Binder.getCallingPid()); + } + + public List<ServiceInfo> getAccessibilityServiceList() { + synchronized (mLock) { + return mInstalledServices; + } + } + + public void interrupt() { + synchronized (mLock) { + for (int i = 0, count = mServices.size(); i < count; i++) { + Service service = mServices.get(i); + try { + service.mServiceInterface.onInterrupt(); + } catch (RemoteException re) { + if (re instanceof DeadObjectException) { + Log.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up."); + if (removeDeadServiceLocked(service)) { + count--; + i--; + } + } else { + Log.e(LOG_TAG, "Error during sending interrupt request to " + + service.mService, re); + } + } + } + } + } + + public void executeMessage(Message message) { + switch (message.what) { + case DO_SET_SERVICE_INFO: + SomeArgs arguments = ((SomeArgs) message.obj); + + AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1; + Service service = (Service) arguments.arg2; + + synchronized (mLock) { + service.mEventTypes = info.eventTypes; + service.mFeedbackType = info.feedbackType; + String[] packageNames = info.packageNames; + if (packageNames != null) { + service.mPackageNames.addAll(Arrays.asList(packageNames)); + } + service.mNotificationTimeout = info.notificationTimeout; + service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0; + } + return; + default: + Log.w(LOG_TAG, "Unknown message type: " + message.what); + } + } + + /** + * Populates the cached list of installed {@link AccessibilityService}s. + */ + private void populateAccessibilityServiceListLocked() { + mInstalledServices.clear(); + + List<ResolveInfo> installedServices = mPackageManager.queryIntentServices( + new Intent(AccessibilityService.SERVICE_INTERFACE), PackageManager.GET_SERVICES); + + for (int i = 0, count = installedServices.size(); i < count; i++) { + mInstalledServices.add(installedServices.get(i).serviceInfo); + } + } + + /** + * Performs {@link AccessibilityService}s delayed notification. The delay is configurable + * and denotes the period after the last event before notifying the service. + * + * @param event The event. + * @param isDefault True to notify default listeners, not default services. + */ + private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, + boolean isDefault) { + for (int i = 0, count = mServices.size(); i < count; i++) { + Service service = mServices.get(i); + + if (service.mIsDefault == isDefault) { + if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) { + mHandledFeedbackTypes |= service.mFeedbackType; + notifyAccessibilityServiceDelayedLocked(service, event); + } + } + } + } + + /** + * Performs an {@link AccessibilityService} delayed notification. The delay is configurable + * and denotes the period after the last event before notifying the service. + * + * @param service The service. + * @param event The event. + */ + private void notifyAccessibilityServiceDelayedLocked(Service service, + AccessibilityEvent event) { + synchronized (mLock) { + int eventType = event.getEventType(); + AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType); + service.mPendingEvents.put(eventType, event); + + int what = eventType | (service.mId << 16); + if (oldEvent != null) { + mHandler.removeMessages(what); + tryRecycleLocked(oldEvent); + } + + Message message = mHandler.obtainMessage(what, service); + message.arg1 = event.getEventType(); + mHandler.sendMessageDelayed(message, service.mNotificationTimeout); + } + } + + /** + * Recycles an event if it can be safely recycled. The condition is that no + * not notified service is interested in the event. + * + * @param event The event. + */ + private void tryRecycleLocked(AccessibilityEvent event) { + int eventType = event.getEventType(); + List<Service> services = mServices; + + // linear in the number of service which is not large + for (int i = 0, count = services.size(); i < count; i++) { + Service service = services.get(i); + if (service.mPendingEvents.get(eventType) == event) { + return; + } + } + + event.recycle(); + } + + /** + * Notifies a service for a scheduled event given the event type. + * + * @param service The service. + * @param eventType The type of the event to dispatch. + */ + private void notifyEventListenerLocked(Service service, int eventType) { + IEventListener listener = service.mServiceInterface; + AccessibilityEvent event = service.mPendingEvents.get(eventType); + + try { + listener.onAccessibilityEvent(event); + if (LOGV) { + Log.i(LOG_TAG, "Event " + event + " sent to " + listener); + } + } catch (RemoteException re) { + if (re instanceof DeadObjectException) { + Log.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up."); + synchronized (mLock) { + removeDeadServiceLocked(service); + } + } else { + Log.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re); + } + } + } + + /** + * Removes a dead service. + * + * @param service The service. + * @return True if the service was removed, false otherwise. + */ + private boolean removeDeadServiceLocked(Service service) { + mServices.remove(service); + mHandler.removeMessages(service.mId); + + if (LOGV) { + Log.i(LOG_TAG, "Dead service " + service.mService + " removed"); + } + + if (mServices.isEmpty()) { + mIsEnabled = false; + updateClientsLocked(); + } + + return true; + } + + /** + * Determines if given event can be dispatched to a service based on the package of the + * event source and already notified services for that event type. Specifically, a + * service is notified if it is interested in events from the package and no other service + * providing the same feedback type has been notified. Exception are services the + * provide generic feedback (feedback type left as a safety net for unforeseen feedback + * types) which are always notified. + * + * @param service The potential receiver. + * @param event The event. + * @param handledFeedbackTypes The feedback types for which services have been notified. + * @return True if the listener should be notified, false otherwise. + */ + private boolean canDispathEventLocked(Service service, AccessibilityEvent event, + int handledFeedbackTypes) { + + if (!service.isConfigured()) { + return false; + } + + if (!service.mService.isBinderAlive()) { + removeDeadServiceLocked(service); + return false; + } + + int eventType = event.getEventType(); + if ((service.mEventTypes & eventType) != eventType) { + return false; + } + + Set<String> packageNames = service.mPackageNames; + CharSequence packageName = event.getPackageName(); + + if (packageNames.isEmpty() || packageNames.contains(packageName)) { + int feedbackType = service.mFeedbackType; + if ((handledFeedbackTypes & feedbackType) != feedbackType + || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) { + return true; + } + } + + return false; + } + + /** + * Manages services by starting enabled ones and stopping disabled ones. + */ + private void manageServicesLocked() { + populateEnabledServicesLocked(mEnabledServices); + updateServicesStateLocked(mInstalledServices, mEnabledServices); + } + + /** + * Unbinds all bound services. + */ + private void unbindAllServicesLocked() { + List<Service> services = mServices; + + for (int i = 0, count = services.size(); i < count; i++) { + Service service = services.get(i); + + service.unbind(); + mComponentNameToServiceMap.remove(service.mComponentName); + } + services.clear(); + } + + /** + * Populates a list with the {@link ComponentName}s of all enabled + * {@link AccessibilityService}s. + * + * @param enabledServices The list. + */ + private void populateEnabledServicesLocked(Set<ComponentName> enabledServices) { + enabledServices.clear(); + + String servicesValue = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + + if (servicesValue != null) { + TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; + splitter.setString(servicesValue); + while (splitter.hasNext()) { + ComponentName enabledService = ComponentName.unflattenFromString(splitter.next()); + enabledServices.add(enabledService); + } + } + } + + /** + * Updates the state of each service by starting (or keeping running) enabled ones and + * stopping the rest. + * + * @param installedServices All installed {@link AccessibilityService}s. + * @param enabledServices The {@link ComponentName}s of the enabled services. + */ + private void updateServicesStateLocked(List<ServiceInfo> installedServices, + Set<ComponentName> enabledServices) { + + Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap; + List<Service> services = mServices; + + for (int i = 0, count = installedServices.size(); i < count; i++) { + ServiceInfo intalledService = installedServices.get(i); + ComponentName componentName = new ComponentName(intalledService.packageName, + intalledService.name); + Service service = componentNameToServiceMap.get(componentName); + + if (enabledServices.contains(componentName)) { + if (service == null) { + new Service(componentName).bind(); + } + } else { + if (service != null) { + service.unbind(); + componentNameToServiceMap.remove(componentName); + services.remove(service); + } + } + } + } + + /** + * Updates the state of {@link android.view.accessibility.AccessibilityManager} clients. + */ + private void updateClientsLocked() { + for (int i = 0, count = mClients.size(); i < count; i++) { + try { + mClients.get(i).setEnabled(mIsEnabled); + } catch (RemoteException re) { + mClients.remove(i); + count--; + } + } + } + + /** + * This class represents an accessibility service. It stores all per service + * data required for the service management, provides API for starting/stopping the + * service and is responsible for adding/removing the service in the data structures + * for service management. The class also exposes configuration interface that is + * passed to the service it represents as soon it is bound. It also serves as the + * connection for the service. + */ + class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection { + int mId = 0; + + IBinder mService; + + IEventListener mServiceInterface; + + int mEventTypes; + + int mFeedbackType; + + Set<String> mPackageNames = new HashSet<String>(); + + boolean mIsDefault; + + long mNotificationTimeout; + + boolean mIsActive; + + ComponentName mComponentName; + + Intent mIntent; + + // the events pending events to be dispatched to this service + final SparseArray<AccessibilityEvent> mPendingEvents = + new SparseArray<AccessibilityEvent>(); + + Service(ComponentName componentName) { + mId = sIdCounter++; + mComponentName = componentName; + mIntent = new Intent().setComponent(mComponentName); + } + + /** + * Binds to the accessibility service. + */ + public void bind() { + if (mService == null) { + mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); + } + } + + /** + * Unbinds form the accessibility service and removes it from the data + * structures for service management. + */ + public void unbind() { + if (mService != null) { + mContext.unbindService(this); + } + } + + /** + * Returns if the service is configured i.e. at least event types of interest + * and feedback type must be set. + * + * @return True if the service is configured, false otherwise. + */ + public boolean isConfigured() { + return (mEventTypes != 0 && mFeedbackType != 0); + } + + public void setServiceInfo(AccessibilityServiceInfo info) { + mCaller.obtainMessageOO(DO_SET_SERVICE_INFO, info, this).sendToTarget(); + } + + public void onServiceConnected(ComponentName componentName, IBinder service) { + mService = service; + mServiceInterface = IEventListener.Stub.asInterface(service); + + try { + mServiceInterface.setConnection(this); + synchronized (mLock) { + if (!mServices.contains(this)) { + mServices.add(this); + mComponentNameToServiceMap.put(componentName, this); + } + } + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while setting Controller for service: " + service, re); + } + } + + public void onServiceDisconnected(ComponentName componentName) { + synchronized (mLock) { + Service service = mComponentNameToServiceMap.remove(componentName); + mServices.remove(service); + } + } + } +} diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 6ed8b4c..4a2808b 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -16,6 +16,10 @@ package com.android.server; +import com.android.server.status.IconData; +import com.android.server.status.NotificationData; +import com.android.server.status.StatusBarService; + import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.INotificationManager; @@ -30,33 +34,29 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; -import android.media.AudioManager; import android.media.AsyncPlayer; -import android.media.RingtoneManager; +import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; import android.os.Binder; -import android.os.RemoteException; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Power; +import android.os.RemoteException; import android.os.Vibrator; import android.provider.Settings; -import android.util.Config; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.Toast; -import com.android.server.status.IconData; -import com.android.server.status.NotificationData; -import com.android.server.status.StatusBarService; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.io.IOException; class NotificationManagerService extends INotificationManager.Stub { @@ -98,7 +98,7 @@ class NotificationManagerService extends INotificationManager.Stub private boolean mBatteryLow; private boolean mBatteryFull; private NotificationRecord mLedNotification; - + private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on @@ -594,6 +594,9 @@ class NotificationManagerService extends INotificationManager.Stub Binder.restoreCallingIdentity(identity); } } + + sendAccessibilityEventTypeNotificationChangedDoCheck(notification, pkg); + } else { if (old != null && old.statusBarKey != null) { long identity = Binder.clearCallingIdentity(); @@ -676,6 +679,26 @@ class NotificationManagerService extends INotificationManager.Stub idOut[0] = id; } + private void sendAccessibilityEventTypeNotificationChangedDoCheck(Notification notification, + CharSequence packageName) { + AccessibilityManager manager = AccessibilityManager.getInstance(mContext); + if (!manager.isEnabled()) { + return; + } + + AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + event.setPackageName(packageName); + event.setClassName(Notification.class.getName()); + event.setParcelableData(notification); + CharSequence tickerText = notification.tickerText; + if (!TextUtils.isEmpty(tickerText)) { + event.getText().add(tickerText); + } + + manager.sendAccessibilityEvent(event); + } + private void cancelNotificationLocked(NotificationRecord r) { // status bar if (r.notification.icon != 0) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 8b7260b..f6c1525 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -228,6 +228,14 @@ class ServerThread extends Thread { } try { + Log.i(TAG, "Starting Accessibility Manager."); + ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, + new AccessibilityManagerService(context)); + } catch (Throwable e) { + Log.e(TAG, "Failure starting Accessibility Manager", e); + } + + try { Log.i(TAG, "Starting Notification Manager."); ServiceManager.addService(Context.NOTIFICATION_SERVICE, new NotificationManagerService(context, statusBar, hardware)); |