diff options
10 files changed, 1010 insertions, 593 deletions
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 7870031..18503f6 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -42,10 +42,8 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ManifestDigest; -import android.content.pm.UserInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; @@ -453,11 +451,17 @@ final class ApplicationPackageManager extends PackageManager { @Override public ResolveInfo resolveActivity(Intent intent, int flags) { + return resolveActivityAsUser(intent, flags, UserHandle.myUserId()); + } + + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { try { return mPM.resolveIntent( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags, UserHandle.myUserId()); + flags, + userId); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -466,12 +470,12 @@ final class ApplicationPackageManager extends PackageManager { @Override public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { - return queryIntentActivitiesForUser(intent, flags, UserHandle.myUserId()); + return queryIntentActivitiesAsUser(intent, flags, UserHandle.myUserId()); } /** @hide Same as above but for a specific user */ @Override - public List<ResolveInfo> queryIntentActivitiesForUser(Intent intent, + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { try { return mPM.queryIntentActivities( @@ -551,19 +555,24 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { try { return mPM.queryIntentServices( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, - UserHandle.myUserId()); + userId); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override + public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + return queryIntentServicesAsUser(intent, flags, UserHandle.myUserId()); + } + + @Override public ProviderInfo resolveContentProvider(String name, int flags) { try { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index fd488ae..171ff3f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1797,6 +1797,39 @@ public abstract class PackageManager { public abstract ResolveInfo resolveActivity(Intent intent, int flags); /** + * Determine the best action to perform for a given Intent for a given user. This + * is how {@link Intent#resolveActivity} finds an activity if a class has not + * been explicitly specified. + * + * <p><em>Note:</em> if using an implicit Intent (without an explicit ComponentName + * specified), be sure to consider whether to set the {@link #MATCH_DEFAULT_ONLY} + * only flag. You need to do so to resolve the activity in the same way + * that {@link android.content.Context#startActivity(Intent)} and + * {@link android.content.Intent#resolveActivity(PackageManager) + * Intent.resolveActivity(PackageManager)} do.</p> + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags. The most important is + * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only + * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}. + * @param userId The user id. + * + * @return Returns a ResolveInfo containing the final activity intent that + * was determined to be the best action. Returns null if no + * matching activity was found. If multiple matching activities are + * found and there is no default set, returns a ResolveInfo + * containing something else, such as the activity resolver. + * + * @see #MATCH_DEFAULT_ONLY + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + * + * @hide + */ + public abstract ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId); + + /** * Retrieve all activities that can be performed for the given intent. * * @param intent The desired intent as per resolveActivity(). @@ -1836,7 +1869,7 @@ public abstract class PackageManager { * @see #GET_RESOLVED_FILTER * @hide */ - public abstract List<ResolveInfo> queryIntentActivitiesForUser(Intent intent, + public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId); @@ -1944,6 +1977,27 @@ public abstract class PackageManager { int flags); /** + * Retrieve all services that can match the given intent for a given user. + * + * @param intent The desired intent as per resolveService(). + * @param flags Additional option flags. + * @param userId The user id. + * + * @return A List<ResolveInfo> containing one entry for each matching + * ServiceInfo. These are ordered from best to worst match -- that + * is, the first item in the list is what is returned by + * resolveService(). If there are no matching services, an empty + * list is returned. + * + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + * + * @hide + */ + public abstract List<ResolveInfo> queryIntentServicesAsUser(Intent intent, + int flags, int userId); + + /** * Find a single content provider by its base path name. * * @param name The name of the provider to find. diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 77fd12a..732699b 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -27,6 +27,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.util.Log; import android.view.IWindow; import android.view.View; @@ -79,6 +80,8 @@ public final class AccessibilityManager { final IAccessibilityManager mService; + final int mUserId; + final Handler mHandler; boolean mIsEnabled; @@ -129,35 +132,72 @@ public final class AccessibilityManager { } /** + * Creates the singleton AccessibilityManager to be shared across users. This + * has to be called before the local AccessibilityManager is created to ensure + * it registers itself in the system correctly. + * <p> + * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or + * INTERACT_ACROSS_USERS permission. + * </p> + * @param context Context in which this manager operates. + * @throws IllegalStateException if not called before the local + * AccessibilityManager is instantiated. + * + * @hide + */ + public static void createAsSharedAcrossUsers(Context context) { + synchronized (sInstanceSync) { + if (sInstance != null) { + throw new IllegalStateException("AccessibilityManager already created."); + } + createSingletonInstance(context, UserHandle.USER_CURRENT); + } + } + + /** * Get an AccessibilityManager instance (create one if necessary). * + * @param context Context in which this manager operates. + * * @hide */ public static AccessibilityManager getInstance(Context context) { synchronized (sInstanceSync) { if (sInstance == null) { - IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); - IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); - sInstance = new AccessibilityManager(context, service); + createSingletonInstance(context, UserHandle.myUserId()); } } return sInstance; } /** + * Creates the singleton instance. + * + * @param context Context in which this manager operates. + * @param userId The user id under which to operate. + */ + private static void createSingletonInstance(Context context, int userId) { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); + sInstance = new AccessibilityManager(context, service, userId); + } + + /** * Create an instance. * * @param context A {@link Context}. * @param service An interface to the backing service. + * @param userId User id under which to run. * * @hide */ - public AccessibilityManager(Context context, IAccessibilityManager service) { + public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { mHandler = new MyHandler(context.getMainLooper()); mService = service; + mUserId = userId; try { - final int stateFlags = mService.addClient(mClient); + final int stateFlags = mService.addClient(mClient, userId); setState(stateFlags); } catch (RemoteException re) { Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); @@ -222,7 +262,7 @@ public final class AccessibilityManager { // client using it is called through Binder from another process. Example: MMS // app adds a SMS notification and the NotificationManagerService calls this method long identityToken = Binder.clearCallingIdentity(); - doRecycle = mService.sendAccessibilityEvent(event); + doRecycle = mService.sendAccessibilityEvent(event, mUserId); Binder.restoreCallingIdentity(identityToken); if (DEBUG) { Log.i(LOG_TAG, event + " sent"); @@ -244,7 +284,7 @@ public final class AccessibilityManager { throw new IllegalStateException("Accessibility off. Did you forget to check that?"); } try { - mService.interrupt(); + mService.interrupt(mUserId); if (DEBUG) { Log.i(LOG_TAG, "Requested interrupt from all services"); } @@ -280,7 +320,7 @@ public final class AccessibilityManager { public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { List<AccessibilityServiceInfo> services = null; try { - services = mService.getInstalledAccessibilityServiceList(); + services = mService.getInstalledAccessibilityServiceList(mUserId); if (DEBUG) { Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } @@ -307,7 +347,7 @@ public final class AccessibilityManager { int feedbackTypeFlags) { List<AccessibilityServiceInfo> services = null; try { - services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags); + services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId); if (DEBUG) { Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } @@ -385,7 +425,7 @@ public final class AccessibilityManager { public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { try { - return mService.addAccessibilityInteractionConnection(windowToken, connection); + return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 5b5134a..60238627 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -34,18 +34,18 @@ import android.view.IWindow; */ interface IAccessibilityManager { - int addClient(IAccessibilityManagerClient client); + int addClient(IAccessibilityManagerClient client, int userId); - boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent); + boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); - List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(); + List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId); - List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType); + List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId); - void interrupt(); + void interrupt(int userId); int addAccessibilityInteractionConnection(IWindow windowToken, - in IAccessibilityInteractionConnection connection); + in IAccessibilityInteractionConnection connection, int userId); void removeAccessibilityInteractionConnection(IWindow windowToken); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index 1bde949..427fe91 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -32,6 +32,7 @@ import android.os.ServiceManager; import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; public class SystemUIService extends Service { static final String TAG = "SystemUIService"; @@ -67,6 +68,10 @@ public class SystemUIService extends Service { @Override public void onCreate() { + // Tell the accessibility layer that this process will + // run as the current user, i.e. run across users. + AccessibilityManager.createAsSharedAcrossUsers(this); + // Pick status bar or system bar. IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); try { diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index e7f3599..abbf0d7 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -50,9 +50,12 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; @@ -76,6 +79,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.R; import com.android.internal.content.PackageMonitor; +import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBarService; import org.xmlpull.v1.XmlPullParserException; @@ -89,6 +93,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; /** * This class is instantiated by the system as a system level service and can be @@ -111,69 +116,62 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private static final int OWN_PROCESS_ID = android.os.Process.myPid(); - private static final int MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG = 1; - - private static final int MSG_TOGGLE_TOUCH_EXPLORATION = 2; - - private static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 3; - - private static final int MSG_SEND_UPDATE_INPUT_FILTER = 4; - private static int sIdCounter = 0; private static int sNextWindowId; - final Context mContext; - - final Object mLock = new Object(); - - final List<Service> mServices = new ArrayList<Service>(); + private final Context mContext; - final List<IAccessibilityManagerClient> mClients = - new ArrayList<IAccessibilityManagerClient>(); + private final Object mLock = new Object(); - final Map<ComponentName, Service> mComponentNameToServiceMap = new HashMap<ComponentName, Service>(); + private final SimpleStringSplitter mStringColonSplitter = + new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); - private final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<AccessibilityServiceInfo>(); + private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = + new ArrayList<AccessibilityServiceInfo>(); - private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); + private final PackageManager mPackageManager; - private final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<ComponentName>(); + private final IWindowManager mWindowManagerService; - private final SparseArray<AccessibilityConnectionWrapper> mWindowIdToInteractionConnectionWrapperMap = - new SparseArray<AccessibilityConnectionWrapper>(); - - private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>(); + private final SecurityPolicy mSecurityPolicy; - private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); + private final MainHandler mMainHandler; - private PackageManager mPackageManager; + private Service mUiAutomationService; - private int mHandledFeedbackTypes = 0; + private Service mQueryBridge; - private boolean mIsAccessibilityEnabled; + private AlertDialog mEnableTouchExplorationDialog; private AccessibilityInputFilter mInputFilter; private boolean mHasInputFilter; - private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>(); - - private boolean mIsTouchExplorationEnabled; + private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = + new RemoteCallbackList<IAccessibilityManagerClient>(); - private boolean mIsScreenMagnificationEnabled; + private final SparseArray<AccessibilityConnectionWrapper> mGlobalInteractionConnections = + new SparseArray<AccessibilityConnectionWrapper>(); - private final IWindowManager mWindowManager; + private final SparseArray<IBinder> mGlobalWindowTokens = new SparseArray<IBinder>(); - private final SecurityPolicy mSecurityPolicy; - - private final MainHandler mMainHandler; + private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); - private Service mUiAutomationService; + private int mCurrentUserId = UserHandle.USER_NULL; - private Service mQueryBridge; + private UserState getCurrentUserStateLocked() { + return getUserStateLocked(mCurrentUserId); + } - private AlertDialog mEnableTouchExplorationDialog; + private UserState getUserStateLocked(int userId) { + UserState state = mUserStates.get(userId); + if (state == null) { + state = new UserState(userId); + mUserStates.put(userId, state); + } + return state; + } /** * Creates a new instance. @@ -183,28 +181,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); - mWindowManager = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE); + mWindowManagerService = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE); mSecurityPolicy = new SecurityPolicy(); mMainHandler = new MainHandler(mContext.getMainLooper()); - registerPackageChangeAndBootCompletedBroadcastReceiver(); - registerSettingsContentObservers(); + registerBroadcastReceivers(); + new AccessibilityContentObserver(mMainHandler).register( + context.getContentResolver()); } - /** - * Registers a {@link BroadcastReceiver} for the events of - * adding/changing/removing/restarting a package and boot completion. - */ - private void registerPackageChangeAndBootCompletedBroadcastReceiver() { - Context context = mContext; - + private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { synchronized (mLock) { + if (getChangingUserId() != mCurrentUserId) { + return; + } // We will update when the automation service dies. if (mUiAutomationService == null) { - populateInstalledAccessibilityServiceLocked(); - manageServicesLocked(); + UserState userState = getCurrentUserStateLocked(); + populateInstalledAccessibilityServiceLocked(userState); + manageServicesLocked(userState); } } } @@ -212,7 +209,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public void onPackageRemoved(String packageName, int uid) { synchronized (mLock) { - Iterator<ComponentName> it = mEnabledServices.iterator(); + final int userId = getChangingUserId(); + if (userId != mCurrentUserId) { + return; + } + UserState state = getUserStateLocked(userId); + Iterator<ComponentName> it = state.mEnabledServices.iterator(); while (it.hasNext()) { ComponentName comp = it.next(); String compPkg = comp.getPackageName(); @@ -221,13 +223,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // Update the enabled services setting. persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - mEnabledServices); + state.mEnabledServices, userId); // Update the touch exploration granted services setting. - mTouchExplorationGrantedServices.remove(comp); + state.mTouchExplorationGrantedServices.remove(comp); persistComponentNamesToSettingLocked( Settings.Secure. TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - mEnabledServices); + state.mEnabledServices, userId); return; } } @@ -238,7 +240,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { synchronized (mLock) { - Iterator<ComponentName> it = mEnabledServices.iterator(); + final int userId = getChangingUserId(); + if (userId != mCurrentUserId) { + return false; + } + UserState state = getUserStateLocked(userId); + Iterator<ComponentName> it = state.mEnabledServices.iterator(); while (it.hasNext()) { ComponentName comp = it.next(); String compPkg = comp.getPackageName(); @@ -250,179 +257,97 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { it.remove(); persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - mEnabledServices); + state.mEnabledServices, userId); } } } return false; } } - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) { - synchronized (mLock) { - // We will update when the automation service dies. - if (mUiAutomationService == null) { - updateInternalStateLocked(); - } - } - return; - } - super.onReceive(context, intent); - } }; // package changes - monitor.register(context, null, true); + monitor.register(mContext, null, UserHandle.ALL, true); - // boot completed - IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); - mContext.registerReceiver(monitor, bootFiler, null, monitor.getRegisteredHandler()); - } - - /** - * {@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 accessibilityEnabledUri = Settings.Secure.getUriFor( - Settings.Secure.ACCESSIBILITY_ENABLED); - contentResolver.registerContentObserver(accessibilityEnabledUri, false, - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - synchronized (mLock) { - // We will update when the automation service dies. - if (mUiAutomationService == null) { - handleAccessibilityEnabledSettingChangedLocked(); - updateInputFilterLocked(); - sendStateToClientsLocked(); - } - } - } - }); + // user change + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_SWITCHED); + userFilter.addAction(Intent.ACTION_USER_REMOVED); - Uri touchExplorationRequestedUri = Settings.Secure.getUriFor( - Settings.Secure.TOUCH_EXPLORATION_ENABLED); - contentResolver.registerContentObserver(touchExplorationRequestedUri, false, - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - synchronized (mLock) { - // We will update when the automation service dies. - if (mUiAutomationService == null) { - handleTouchExplorationEnabledSettingChangedLocked(); - updateInputFilterLocked(); - sendStateToClientsLocked(); - } - } - } - }); - - Uri accessibilityScreenMagnificationEnabledUri = Settings.Secure.getUriFor( - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); - contentResolver.registerContentObserver(accessibilityScreenMagnificationEnabledUri, false, - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - synchronized (mLock) { - // We will update when the automation service dies. - if (mUiAutomationService == null) { - handleScreenMagnificationEnabledSettingChangedLocked(); - updateInputFilterLocked(); - sendStateToClientsLocked(); - } - } - } - }); - - Uri accessibilityServicesUri = - Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); - contentResolver.registerContentObserver(accessibilityServicesUri, false, - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - synchronized (mLock) { - // We will update when the automation service dies. - if (mUiAutomationService == null) { - populateEnabledAccessibilityServicesLocked(); - manageServicesLocked(); - } - } - } - }); - - Uri touchExplorationGrantedServicesUri = Settings.Secure.getUriFor( - Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES); - contentResolver.registerContentObserver(touchExplorationGrantedServicesUri, false, - new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - synchronized (mLock) { - // We will update when the automation service dies. - if (mUiAutomationService == null) { - populateTouchExplorationGrantedAccessibilityServicesLocked(); - handleTouchExplorationGrantedAccessibilityServicesChangedLocked(); - } - } + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } - }); + } + }, userFilter); } - public int addClient(IAccessibilityManagerClient client) throws RemoteException { + public int addClient(IAccessibilityManagerClient client, int userId) { synchronized (mLock) { - final IAccessibilityManagerClient addedClient = client; - mClients.add(addedClient); - // Clients are registered all the time until their process is - // killed, therefore we do not have a corresponding unlinkToDeath. - client.asBinder().linkToDeath(new DeathRecipient() { - public void binderDied() { - synchronized (mLock) { - addedClient.asBinder().unlinkToDeath(this, 0); - mClients.remove(addedClient); - } - } - }, 0); - return getState(); + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked(userId); + // If the client is from a process that runs across users such as + // the system UI or the system we add it to the global state that + // is shared across users. + UserState userState = getUserStateLocked(resolvedUserId); + if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { + mGlobalClients.register(client); + return getClientState(userState); + } else { + userState.mClients.register(client); + // If this client is not for the current user we do not + // return a state since it is not for the foreground user. + // We will send the state to the client on a user switch. + return (resolvedUserId == mCurrentUserId) ? getClientState(userState) : 0; + } } } - public boolean sendAccessibilityEvent(AccessibilityEvent event) { + public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) { synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked(userId); + // This method does nothing for a background user. + if (resolvedUserId != mCurrentUserId) { + return true; // yes, recycle the event + } if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) { mSecurityPolicy.updateActiveWindowAndEventSourceLocked(event); notifyAccessibilityServicesDelayedLocked(event, false); notifyAccessibilityServicesDelayedLocked(event, true); } if (mHasInputFilter && mInputFilter != null) { - mMainHandler.obtainMessage(MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, + mMainHandler.obtainMessage(MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, AccessibilityEvent.obtain(event)).sendToTarget(); } event.recycle(); - mHandledFeedbackTypes = 0; + getUserStateLocked(resolvedUserId).mHandledFeedbackTypes = 0; } return (OWN_PROCESS_ID != Binder.getCallingPid()); } - public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { + public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { synchronized (mLock) { - return mInstalledServices; + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked(userId); + return getUserStateLocked(resolvedUserId).mInstalledServices; } } - public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) { - List<AccessibilityServiceInfo> result = mEnabledServicesForFeedbackTempList; - result.clear(); - List<Service> services = mServices; + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, + int userId) { + List<AccessibilityServiceInfo> result = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked(userId); + result = mEnabledServicesForFeedbackTempList; + result.clear(); + List<Service> services = getUserStateLocked(resolvedUserId).mServices; while (feedbackType != 0) { final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType)); feedbackType &= ~feedbackTypeBit; @@ -438,30 +363,51 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return result; } - public void interrupt() { + public void interrupt(int userId) { + CopyOnWriteArrayList<Service> services; synchronized (mLock) { - for (int i = 0, count = mServices.size(); i < count; i++) { - Service service = mServices.get(i); - try { - service.mServiceInterface.onInterrupt(); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending interrupt request to " - + service.mService, re); - } + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked(userId); + // This method does nothing for a background user. + if (resolvedUserId != mCurrentUserId) { + return; + } + services = getUserStateLocked(resolvedUserId).mServices; + } + for (int i = 0, count = services.size(); i < count; i++) { + Service service = services.get(i); + try { + service.mServiceInterface.onInterrupt(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error during sending interrupt request to " + + service.mService, re); } } } public int addAccessibilityInteractionConnection(IWindow windowToken, - IAccessibilityInteractionConnection connection) throws RemoteException { + IAccessibilityInteractionConnection connection, int userId) throws RemoteException { synchronized (mLock) { - final IWindow addedWindowToken = windowToken; + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked(userId); final int windowId = sNextWindowId++; - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper(windowId, - connection); - wrapper.linkToDeath(); - mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder()); - mWindowIdToInteractionConnectionWrapperMap.put(windowId, wrapper); + // If the window is from a process that runs across users such as + // the system UI or the system we add it to the global state that + // is shared across users. + if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { + AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( + windowId, connection, UserHandle.USER_ALL); + wrapper.linkToDeath(); + mGlobalInteractionConnections.put(windowId, wrapper); + mGlobalWindowTokens.put(windowId, windowToken.asBinder()); + } else { + AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( + windowId, connection, resolvedUserId); + wrapper.linkToDeath(); + UserState userState = getUserStateLocked(resolvedUserId); + userState.mInteractionConnections.put(windowId, wrapper); + userState.mWindowTokens.put(windowId, windowToken.asBinder()); + } if (DEBUG) { Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId); } @@ -469,22 +415,47 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - public void removeAccessibilityInteractionConnection(IWindow windowToken) { + public void removeAccessibilityInteractionConnection(IWindow window) { synchronized (mLock) { - final int count = mWindowIdToWindowTokenMap.size(); - for (int i = 0; i < count; i++) { - if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) { - final int windowId = mWindowIdToWindowTokenMap.keyAt(i); - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(windowId); - wrapper.unlinkToDeath(); - removeAccessibilityInteractionConnectionLocked(windowId); + mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + IBinder token = window.asBinder(); + final boolean removedGlobal = + removeAccessibilityInteractionConnectionInternalLocked( + token, mGlobalWindowTokens, mGlobalInteractionConnections); + if (removedGlobal) { + return; + } + final int userCount = mUserStates.size(); + for (int i = 0; i < userCount; i++) { + UserState userState = mUserStates.valueAt(i); + final boolean removedForUser = + removeAccessibilityInteractionConnectionInternalLocked( + token, userState.mWindowTokens, userState.mInteractionConnections); + if (removedForUser) { return; } } } } + private boolean removeAccessibilityInteractionConnectionInternalLocked(IBinder windowToken, + SparseArray<IBinder> windowTokens, + SparseArray<AccessibilityConnectionWrapper> interactionConnections) { + final int count = windowTokens.size(); + for (int i = 0; i < count; i++) { + if (windowTokens.valueAt(i) == windowToken) { + final int windowId = windowTokens.keyAt(i); + windowTokens.removeAt(i); + AccessibilityConnectionWrapper wrapper = interactionConnections.get(windowId); + wrapper.unlinkToDeath(); + interactionConnections.remove(windowId); + return true; + } + } + return false; + } + public void registerUiTestAutomationService(IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, @@ -495,21 +466,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // If an automation services is connected to the system all services are stopped // so the automation one is the only one running. Settings are not changed so when // the automation service goes away the state is restored from the settings. + UserState userState = getCurrentUserStateLocked(); + unbindAllServicesLocked(userState); - // Disable all services. - final int runningServiceCount = mServices.size(); - for (int i = 0; i < runningServiceCount; i++) { - Service runningService = mServices.get(i); - runningService.unbind(); - } // If necessary enable accessibility and announce that. - if (!mIsAccessibilityEnabled) { - mIsAccessibilityEnabled = true; - sendStateToClientsLocked(); + if (!userState.mIsAccessibilityEnabled) { + userState.mIsAccessibilityEnabled = true; + scheduleSendStateToClientsLocked(userState); } } // Hook the automation service up. - mUiAutomationService = new Service(componentName, accessibilityServiceInfo, true); + mUiAutomationService = new Service(mCurrentUserId, componentName, + accessibilityServiceInfo, true); mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder()); } @@ -572,30 +540,80 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @param outBounds The output to which to write the bounds. */ boolean getActiveWindowBounds(Rect outBounds) { + IBinder token; synchronized (mLock) { final int windowId = mSecurityPolicy.mActiveWindowId; - IBinder token = mWindowIdToWindowTokenMap.get(windowId); - try { - WindowInfo info = mWindowManager.getWindowInfo(token); - if (info != null) { - outBounds.set(info.frame); - return true; - } - } catch (RemoteException re) { - /* ignore */ + token = mGlobalWindowTokens.get(windowId); + if (token == null) { + token = getCurrentUserStateLocked().mWindowTokens.get(windowId); + } + } + WindowInfo info = null; + try { + info = mWindowManagerService.getWindowInfo(token); + if (info != null) { + outBounds.set(info.frame); + return true; + } + } catch (RemoteException re) { + /* ignore */ + } finally { + if (info != null) { + info.recycle(); } - return false; } + return false; } - public int getActiveWindowId() { + int getActiveWindowId() { return mSecurityPolicy.mActiveWindowId; } + private void switchUser(int userId) { + synchronized (mLock) { + if (userId == mCurrentUserId) { + return; + } + + // Disconnect from services for the old user. + UserState oldUserState = getUserStateLocked(mCurrentUserId); + unbindAllServicesLocked(oldUserState); + + // Disable the local managers for the old user. + if (oldUserState.mClients.getRegisteredCallbackCount() > 0) { + mMainHandler.obtainMessage(MainHandler.MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER, + oldUserState.mUserId, 0).sendToTarget(); + } + + // The user changed. + mCurrentUserId = userId; + + // Recreate the internal state for the new user. + mMainHandler.obtainMessage(MainHandler.MSG_SEND_RECREATE_INTERNAL_STATE, + mCurrentUserId, 0).sendToTarget(); + + // Re-register the test automation service after the new state is recreated. + if (mUiAutomationService != null) { + unregisterUiTestAutomationService(mUiAutomationService.mServiceInterface); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mUiAutomationService.mServiceInterface; + args.arg2 = mUiAutomationService.mAccessibilityServiceInfo; + mMainHandler.obtainMessage(MainHandler.MSG_REGISTER_UI_TEST_AUTOMATION_SERVICE, + args).sendToTarget(); + } + } + } + + private void removeUser(int userId) { + synchronized (mLock) { + mUserStates.remove(userId); + } + } + private Service getQueryBridge() { if (mQueryBridge == null) { AccessibilityServiceInfo info = new AccessibilityServiceInfo(); - mQueryBridge = new Service(null, info, true); + mQueryBridge = new Service(UserHandle.USER_NULL, null, info, true); } return mQueryBridge; } @@ -610,8 +628,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // gestures to avoid user frustration when different // behavior is observed from different combinations of // enabled accessibility services. - for (int i = mServices.size() - 1; i >= 0; i--) { - Service service = mServices.get(i); + UserState state = getCurrentUserStateLocked(); + for (int i = state.mServices.size() - 1; i >= 0; i--) { + Service service = state.mServices.get(i); if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) { service.notifyGesture(gestureId); return true; @@ -624,29 +643,36 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * Removes an AccessibilityInteractionConnection. * * @param windowId The id of the window to which the connection is targeted. + * @param userId The id of the user owning the connection. UserHandle.USER_ALL + * if global. */ - private void removeAccessibilityInteractionConnectionLocked(int windowId) { - mWindowIdToWindowTokenMap.remove(windowId); - mWindowIdToInteractionConnectionWrapperMap.remove(windowId); + private void removeAccessibilityInteractionConnectionLocked(int windowId, int userId) { + if (userId == UserHandle.USER_ALL) { + mGlobalWindowTokens.remove(windowId); + mGlobalInteractionConnections.remove(windowId); + } else { + UserState userState = getCurrentUserStateLocked(); + userState.mWindowTokens.remove(windowId); + userState.mInteractionConnections.remove(windowId); + } if (DEBUG) { Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId); } } - /** - * Populates the cached list of installed {@link AccessibilityService}s. - */ - private void populateInstalledAccessibilityServiceLocked() { - mInstalledServices.clear(); + private void populateInstalledAccessibilityServiceLocked(UserState userState) { + userState.mInstalledServices.clear(); - List<ResolveInfo> installedServices = mPackageManager.queryIntentServices( + List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( new Intent(AccessibilityService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + mCurrentUserId); for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); ServiceInfo serviceInfo = resolveInfo.serviceInfo; - if (!android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals(serviceInfo.permission)) { + if (!android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals( + serviceInfo.permission)) { Slog.w(LOG_TAG, "Skipping accessibilty service " + new ComponentName( serviceInfo.packageName, serviceInfo.name).flattenToShortString() + ": it does not require the permission " @@ -656,7 +682,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityServiceInfo accessibilityServiceInfo; try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); - mInstalledServices.add(accessibilityServiceInfo); + userState.mInstalledServices.add(accessibilityServiceInfo); } catch (XmlPullParserException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); } catch (IOException ioe) { @@ -665,16 +691,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - private void populateEnabledAccessibilityServicesLocked() { + private void populateEnabledAccessibilityServicesLocked(UserState userState) { populateComponentNamesFromSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - mEnabledServices); + userState.mUserId, + userState.mEnabledServices); } - private void populateTouchExplorationGrantedAccessibilityServicesLocked() { + private void populateTouchExplorationGrantedAccessibilityServicesLocked( + UserState userState) { populateComponentNamesFromSettingLocked( Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - mTouchExplorationGrantedServices); + userState.mUserId, + userState.mTouchExplorationGrantedServices); } /** @@ -687,12 +716,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, boolean isDefault) { try { - for (int i = 0, count = mServices.size(); i < count; i++) { - Service service = mServices.get(i); + UserState state = getCurrentUserStateLocked(); + for (int i = 0, count = state.mServices.size(); i < count; i++) { + Service service = state.mServices.get(i); if (service.mIsDefault == isDefault) { - if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) { - mHandledFeedbackTypes |= service.mFeedbackType; + if (canDispathEventLocked(service, event, state.mHandledFeedbackTypes)) { + state.mHandledFeedbackTypes |= service.mFeedbackType; service.notifyAccessibilityEvent(event); } } @@ -706,19 +736,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** - * Adds a service. + * Adds a service for a user. * * @param service The service to add. + * @param userId The user id. */ - private void tryAddServiceLocked(Service service) { + private void tryAddServiceLocked(Service service, int userId) { try { - if (mServices.contains(service) || !service.isConfigured()) { + UserState userState = getUserStateLocked(userId); + if (userState.mServices.contains(service) || !service.isConfigured()) { return; } service.linkToOwnDeath(); - mServices.add(service); - mComponentNameToServiceMap.put(service.mComponentName, service); - updateInputFilterLocked(); + userState.mServices.add(service); + userState.mComponentNameToServiceMap.put(service.mComponentName, service); + updateInputFilterLocked(userState); tryEnableTouchExplorationLocked(service); } catch (RemoteException e) { /* do nothing */ @@ -732,14 +764,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @return True if the service was removed, false otherwise. */ private boolean tryRemoveServiceLocked(Service service) { - final boolean removed = mServices.remove(service); + UserState userState = getUserStateLocked(service.mUserId); + final boolean removed = userState.mServices.remove(service); if (!removed) { return false; } - mComponentNameToServiceMap.remove(service.mComponentName); + userState.mComponentNameToServiceMap.remove(service.mComponentName); service.unlinkToOwnDeath(); service.dispose(); - updateInputFilterLocked(); + updateInputFilterLocked(userState); tryDisableTouchExplorationLocked(service); return removed; } @@ -791,23 +824,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { /** * Manages services by starting enabled ones and stopping disabled ones. */ - private void manageServicesLocked() { - final int enabledInstalledServicesCount = updateServicesStateLocked(mInstalledServices, - mEnabledServices); + private void manageServicesLocked(UserState userState) { + final int enabledInstalledServicesCount = updateServicesStateLocked(userState); // No enabled installed services => disable accessibility to avoid // sending accessibility events with no recipient across processes. - if (mIsAccessibilityEnabled && enabledInstalledServicesCount == 0) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0); + if (userState.mIsAccessibilityEnabled && enabledInstalledServicesCount == 0) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId); } } /** - * Unbinds all bound services. + * Unbinds all bound services for a user. + * + * @param userState The user state. */ - private void unbindAllServicesLocked() { - List<Service> services = mServices; - + private void unbindAllServicesLocked(UserState userState) { + List<Service> services = userState.mServices; for (int i = 0, count = services.size(); i < count; i++) { Service service = services.get(i); if (service.unbind()) { @@ -819,17 +852,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { /** * Populates a set with the {@link ComponentName}s stored in a colon - * separated value setting. + * separated value setting for a given user. * * @param settingName The setting to parse. + * @param userId The user id. * @param outComponentNames The output component names. */ - private void populateComponentNamesFromSettingLocked(String settingName, + private void populateComponentNamesFromSettingLocked(String settingName, int userId, Set<ComponentName> outComponentNames) { + String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), + settingName, userId); outComponentNames.clear(); - - String settingValue = Settings.Secure.getString(mContext.getContentResolver(), settingName); - if (settingValue != null) { TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; splitter.setString(settingValue); @@ -854,7 +887,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @param componentNames The component names. */ private void persistComponentNamesToSettingLocked(String settingName, - Set<ComponentName> componentNames) { + Set<ComponentName> componentNames, int userId) { StringBuilder builder = new StringBuilder(); for (ComponentName componentName : componentNames) { if (builder.length() > 0) { @@ -862,34 +895,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } builder.append(componentName.flattenToShortString()); } - Settings.Secure.putString(mContext.getContentResolver(), settingName, builder.toString()); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + settingName, builder.toString(), userId); } /** * 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. + * @param userState The user state for which to do that. * @return The number of enabled installed services. */ - private int updateServicesStateLocked(List<AccessibilityServiceInfo> installedServices, - Set<ComponentName> enabledServices) { - - Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap; - boolean isEnabled = mIsAccessibilityEnabled; + private int updateServicesStateLocked(UserState userState) { + Map<ComponentName, Service> componentNameToServiceMap = + userState.mComponentNameToServiceMap; + boolean isEnabled = userState.mIsAccessibilityEnabled; int enabledInstalledServices = 0; - for (int i = 0, count = installedServices.size(); i < count; i++) { - AccessibilityServiceInfo installedService = installedServices.get(i); + for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { + AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); ComponentName componentName = ComponentName.unflattenFromString( installedService.getId()); Service service = componentNameToServiceMap.get(componentName); if (isEnabled) { - if (enabledServices.contains(componentName)) { + if (userState.mEnabledServices.contains(componentName)) { if (service == null) { - service = new Service(componentName, installedService, false); + service = new Service(userState.mUserId, componentName, + installedService, false); } service.bind(); enabledInstalledServices++; @@ -908,145 +941,206 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return enabledInstalledServices; } - /** - * Sends the state to the clients. - */ - private void sendStateToClientsLocked() { - final int state = getState(); - for (int i = 0, count = mClients.size(); i < count; i++) { + private void scheduleSendStateToClientsLocked(UserState userState) { + if (mGlobalClients.getRegisteredCallbackCount() > 0 + || userState.mClients.getRegisteredCallbackCount() > 0) { + final int clientState = getClientState(userState); + mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS, + clientState, userState.mUserId) .sendToTarget(); + } + } + + private void updateInputFilterLocked(UserState userState) { + boolean setInputFilter = false; + AccessibilityInputFilter inputFilter = null; + synchronized (mLock) { + if ((userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) + || userState.mIsDisplayMagnificationEnabled) { + if (!mHasInputFilter) { + mHasInputFilter = true; + if (mInputFilter == null) { + mInputFilter = new AccessibilityInputFilter(mContext, + AccessibilityManagerService.this); + } + inputFilter = mInputFilter; + setInputFilter = true; + } + int flags = 0; + if (userState.mIsDisplayMagnificationEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; + } + if (userState.mIsTouchExplorationEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; + } + mInputFilter.setEnabledFeatures(flags); + } else { + if (mHasInputFilter) { + mHasInputFilter = false; + mInputFilter.setEnabledFeatures(0); + inputFilter = null; + setInputFilter = true; + } + } + } + if (setInputFilter) { try { - mClients.get(i).setState(state); + mWindowManagerService.setInputFilter(inputFilter); } catch (RemoteException re) { - mClients.remove(i); - count--; - i--; + /* ignore */ } } } - /** - * Gets the current state as a set of flags. - * - * @return The state. - */ - private int getState() { - int state = 0; - if (mIsAccessibilityEnabled) { - state |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; - } - // Touch exploration relies on enabled accessibility. - if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { - state |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; + private void showEnableTouchExplorationDialog(final Service service) { + String label = service.mResolveInfo.loadLabel( + mContext.getPackageManager()).toString(); + synchronized (mLock) { + final UserState state = getCurrentUserStateLocked(); + if (state.mIsTouchExplorationEnabled) { + return; + } + if (mEnableTouchExplorationDialog != null + && mEnableTouchExplorationDialog.isShowing()) { + return; + } + mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // The user allowed the service to toggle touch exploration. + state.mTouchExplorationGrantedServices.add(service.mComponentName); + persistComponentNamesToSettingLocked( + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + state.mTouchExplorationGrantedServices, state.mUserId); + // Enable touch exploration. + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, + service.mUserId); + } + }) + .setNegativeButton(android.R.string.cancel, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setTitle(R.string.enable_explore_by_touch_warning_title) + .setMessage(mContext.getString( + R.string.enable_explore_by_touch_warning_message, label)) + .create(); + mEnableTouchExplorationDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); + mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true); + mEnableTouchExplorationDialog.show(); } - return state; } - /** - * Updates the state of the input filter. - */ - private void updateInputFilterLocked() { - mMainHandler.obtainMessage(MSG_SEND_UPDATE_INPUT_FILTER).sendToTarget(); + private int getClientState(UserState userState) { + int clientState = 0; + if (userState.mIsAccessibilityEnabled) { + clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; + } + // Touch exploration relies on enabled accessibility. + if (userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) { + clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; + } + return clientState; } - /** - * Updated the internal state of this service to match the current settings. - */ - private void updateInternalStateLocked() { - populateInstalledAccessibilityServiceLocked(); - populateEnabledAccessibilityServicesLocked(); - populateTouchExplorationGrantedAccessibilityServicesLocked(); + private void recreateInternalStateLocked(UserState userState) { + populateInstalledAccessibilityServiceLocked(userState); + populateEnabledAccessibilityServicesLocked(userState); + populateTouchExplorationGrantedAccessibilityServicesLocked(userState); - handleTouchExplorationEnabledSettingChangedLocked(); - handleScreenMagnificationEnabledSettingChangedLocked(); - handleAccessibilityEnabledSettingChangedLocked(); + handleTouchExplorationEnabledSettingChangedLocked(userState); + handleDisplayMagnificationEnabledSettingChangedLocked(userState); + handleAccessibilityEnabledSettingChangedLocked(userState); - updateInputFilterLocked(); - sendStateToClientsLocked(); + updateInputFilterLocked(userState); + scheduleSendStateToClientsLocked(userState); } - /** - * Updated the state based on the accessibility enabled setting. - */ - private void handleAccessibilityEnabledSettingChangedLocked() { - mIsAccessibilityEnabled = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; - if (mIsAccessibilityEnabled) { - manageServicesLocked(); + private void handleAccessibilityEnabledSettingChangedLocked(UserState userState) { + userState.mIsAccessibilityEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1; + if (userState.mIsAccessibilityEnabled ) { + manageServicesLocked(userState); } else { - unbindAllServicesLocked(); + unbindAllServicesLocked(userState); } } - /** - * Updates the state based on the touch exploration enabled setting. - */ - private void handleTouchExplorationEnabledSettingChangedLocked() { - mIsTouchExplorationEnabled = Settings.Secure.getInt( + private void handleTouchExplorationEnabledSettingChangedLocked(UserState userState) { + userState.mIsTouchExplorationEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1; } - /** - * Updates the state based on the screen magnification enabled setting. - */ - private void handleScreenMagnificationEnabledSettingChangedLocked() { - mIsScreenMagnificationEnabled = Settings.Secure.getInt( + private void handleDisplayMagnificationEnabledSettingChangedLocked(UserState userState) { + userState.mIsDisplayMagnificationEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1; + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, + 0, userState.mUserId) == 1; } - private void handleTouchExplorationGrantedAccessibilityServicesChangedLocked() { - final int serviceCount = mServices.size(); + private void handleTouchExplorationGrantedAccessibilityServicesChangedLocked( + UserState userState) { + final int serviceCount = userState.mServices.size(); for (int i = 0; i < serviceCount; i++) { - Service service = mServices.get(i); + Service service = userState.mServices.get(i); if (service.mRequestTouchExplorationMode - && mTouchExplorationGrantedServices.contains(service.mComponentName)) { + && userState.mTouchExplorationGrantedServices.contains( + service.mComponentName)) { tryEnableTouchExplorationLocked(service); return; } } - if (mIsTouchExplorationEnabled) { - mMainHandler.obtainMessage(MSG_TOGGLE_TOUCH_EXPLORATION, 0, - 0).sendToTarget(); + if (userState.mIsTouchExplorationEnabled) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId); } } private void tryEnableTouchExplorationLocked(final Service service) { - if (!mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) { - final boolean canToggleTouchExploration = mTouchExplorationGrantedServices.contains( - service.mComponentName); + UserState userState = getUserStateLocked(service.mUserId); + if (!userState.mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) { + final boolean canToggleTouchExploration = + userState.mTouchExplorationGrantedServices.contains(service.mComponentName); if (!service.mIsAutomation && !canToggleTouchExploration) { - mMainHandler.obtainMessage(MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG, - service).sendToTarget(); + showEnableTouchExplorationDialog(service); } else { - mMainHandler.obtainMessage(MSG_TOGGLE_TOUCH_EXPLORATION, 1, 0).sendToTarget(); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, userState.mUserId); } } } private void tryDisableTouchExplorationLocked(Service service) { - if (mIsTouchExplorationEnabled) { - synchronized (mLock) { - final int serviceCount = mServices.size(); - for (int i = 0; i < serviceCount; i++) { - Service other = mServices.get(i); - if (other != service && other.mRequestTouchExplorationMode) { - return; - } + UserState userState = getUserStateLocked(service.mUserId); + if (userState.mIsTouchExplorationEnabled) { + final int serviceCount = userState.mServices.size(); + for (int i = 0; i < serviceCount; i++) { + Service other = userState.mServices.get(i); + if (other != service && other.mRequestTouchExplorationMode) { + return; } - mMainHandler.obtainMessage(MSG_TOGGLE_TOUCH_EXPLORATION, 0, 0).sendToTarget(); } + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId); } } private class AccessibilityConnectionWrapper implements DeathRecipient { private final int mWindowId; + private final int mUserId; private final IAccessibilityInteractionConnection mConnection; public AccessibilityConnectionWrapper(int windowId, - IAccessibilityInteractionConnection connection) { + IAccessibilityInteractionConnection connection, int userId) { mWindowId = windowId; + mUserId = userId; mConnection = connection; } @@ -1062,12 +1156,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void binderDied() { unlinkToDeath(); synchronized (mLock) { - removeAccessibilityInteractionConnectionLocked(mWindowId); + removeAccessibilityInteractionConnectionLocked(mWindowId, mUserId); } } } - private class MainHandler extends Handler { + private final class MainHandler extends Handler { + public static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 1; + public static final int MSG_SEND_STATE_TO_CLIENTS = 2; + public static final int MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER = 3; + public static final int MSG_SEND_RECREATE_INTERNAL_STATE = 4; + public static final int MSG_REGISTER_UI_TEST_AUTOMATION_SERVICE = 5; public MainHandler(Looper looper) { super(looper); @@ -1077,104 +1176,70 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void handleMessage(Message msg) { final int type = msg.what; switch (type) { - case MSG_TOGGLE_TOUCH_EXPLORATION: { - final int value = msg.arg1; - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, value); - } break; - case MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG: { - final Service service = (Service) msg.obj; - String label = service.mResolveInfo.loadLabel( - mContext.getPackageManager()).toString(); - synchronized (mLock) { - if (mIsTouchExplorationEnabled) { - return; - } - if (mEnableTouchExplorationDialog != null - && mEnableTouchExplorationDialog.isShowing()) { - return; - } - mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(android.R.string.ok, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // The user allowed the service to toggle touch exploration. - mTouchExplorationGrantedServices.add(service.mComponentName); - persistComponentNamesToSettingLocked( - Settings.Secure. - TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - mTouchExplorationGrantedServices); - // Enable touch exploration. - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1); - } - }) - .setNegativeButton(android.R.string.cancel, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setTitle(R.string.enable_explore_by_touch_warning_title) - .setMessage(mContext.getString( - R.string.enable_explore_by_touch_warning_message, label)) - .create(); - mEnableTouchExplorationDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); - mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true); - mEnableTouchExplorationDialog.show(); - } - } break; case MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER: { AccessibilityEvent event = (AccessibilityEvent) msg.obj; - if (mHasInputFilter && mInputFilter != null) { - mInputFilter.notifyAccessibilityEvent(event); + synchronized (mLock) { + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.notifyAccessibilityEvent(event); + } } event.recycle(); } break; - case MSG_SEND_UPDATE_INPUT_FILTER: { - boolean setInputFilter = false; - AccessibilityInputFilter inputFilter = null; + case MSG_SEND_STATE_TO_CLIENTS: { + final int clientState = msg.arg1; + final int userId = msg.arg2; + sendStateToClients(clientState, mGlobalClients); + sendStateToClientsForUser(clientState, userId); + } break; + case MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER: { + final int userId = msg.arg1; + sendStateToClientsForUser(0, userId); + } break; + case MSG_SEND_RECREATE_INTERNAL_STATE: { + final int userId = msg.arg1; synchronized (mLock) { - if ((mIsAccessibilityEnabled && mIsTouchExplorationEnabled) - || mIsScreenMagnificationEnabled) { - if (!mHasInputFilter) { - mHasInputFilter = true; - if (mInputFilter == null) { - mInputFilter = new AccessibilityInputFilter(mContext, - AccessibilityManagerService.this); - } - inputFilter = mInputFilter; - setInputFilter = true; - } - int flags = 0; - if (mIsScreenMagnificationEnabled) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; - } - if (mIsTouchExplorationEnabled) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; - } - mInputFilter.setEnabledFeatures(flags); - } else { - if (mHasInputFilter) { - mHasInputFilter = false; - mInputFilter.setEnabledFeatures(0); - inputFilter = null; - setInputFilter = true; - } - } + UserState userState = getUserStateLocked(userId); + recreateInternalStateLocked(userState); } - if (setInputFilter) { - try { - mWindowManager.setInputFilter(inputFilter); - } catch (RemoteException re) { - /* ignore */ - } + } break; + case MSG_REGISTER_UI_TEST_AUTOMATION_SERVICE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + IAccessibilityServiceClient client = + (IAccessibilityServiceClient) args.arg1; + AccessibilityServiceInfo info = (AccessibilityServiceInfo) args.arg2; + registerUiTestAutomationService(client, info); + } finally { + args.recycle(); } } break; } } + + private void sendStateToClientsForUser(int clientState, int userId) { + final UserState userState; + synchronized (mLock) { + userState = getUserStateLocked(userId); + } + sendStateToClients(clientState, userState.mClients); + } + + private void sendStateToClients(int clientState, + RemoteCallbackList<IAccessibilityManagerClient> clients) { + try { + final int userClientCount = clients.beginBroadcast(); + for (int i = 0; i < userClientCount; i++) { + IAccessibilityManagerClient client = clients.getBroadcastItem(i); + try { + client.setState(clientState); + } catch (RemoteException re) { + /* ignore */ + } + } + } finally { + clients.finishBroadcast(); + } + } } /** @@ -1192,6 +1257,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // used as message types allowing us to remove messages per event type. private static final int MSG_ON_GESTURE = 0x80000000; + final int mUserId; + int mId = 0; AccessibilityServiceInfo mAccessibilityServiceInfo; @@ -1250,8 +1317,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } }; - public Service(ComponentName componentName, + public Service(int userId, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) { + mUserId = userId; mResolveInfo = accessibilityServiceInfo.getResolveInfo(); mId = sIdCounter++; mComponentName = componentName; @@ -1312,7 +1380,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { */ public boolean bind() { if (!mIsAutomation && mService == null) { - return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); + return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE, mUserId); } return false; } @@ -1355,17 +1423,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public void setServiceInfo(AccessibilityServiceInfo info) { - synchronized (mLock) { - // If the XML manifest had data to configure the service its info - // should be already set. In such a case update only the dynamically - // configurable properties. - AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; - if (oldInfo != null) { - oldInfo.updateDynamicallyConfigurableProperties(info); - setDynamicallyConfigurableProperties(oldInfo); - } else { - setDynamicallyConfigurableProperties(info); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + // If the XML manifest had data to configure the service its info + // should be already set. In such a case update only the dynamically + // configurable properties. + AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; + if (oldInfo != null) { + oldInfo.updateDynamicallyConfigurableProperties(info); + setDynamicallyConfigurableProperties(oldInfo); + } else { + setDynamicallyConfigurableProperties(info); + } } + } finally { + Binder.restoreCallingIdentity(identity); } } @@ -1376,7 +1449,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { try { mServiceInterface.setConnection(this, mId); synchronized (mLock) { - tryAddServiceLocked(this); + tryAddServiceLocked(this, mUserId); } } catch (RemoteException re) { Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re); @@ -1388,14 +1461,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return -1; + } mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); if (!permissionGranted) { return 0; } else { + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); connection = getConnectionLocked(resolvedWindowId); if (connection == null) { return 0; @@ -1425,10 +1505,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return -1; + } mSecurityPolicy.enforceCanRetrieveWindowContent(this); + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { @@ -1464,10 +1551,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid) throws RemoteException { - final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return -1; + } mSecurityPolicy.enforceCanRetrieveWindowContent(this); + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { @@ -1502,10 +1596,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return -1; + } mSecurityPolicy.enforceCanRetrieveWindowContent(this); + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { @@ -1540,10 +1641,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return -1; + } mSecurityPolicy.enforceCanRetrieveWindowContent(this); + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { @@ -1576,10 +1684,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, int action, Bundle arguments, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) { - final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) + throws RemoteException { + final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return false; + } + mSecurityPolicy.enforceCanRetrieveWindowContent(this); + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, resolvedWindowId, action, arguments); if (!permissionGranted) { @@ -1608,22 +1725,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return true; } - public boolean performGlobalAction(int action) { - switch (action) { - case AccessibilityService.GLOBAL_ACTION_BACK: { - sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); - } return true; - case AccessibilityService.GLOBAL_ACTION_HOME: { - sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); - } return true; - case AccessibilityService.GLOBAL_ACTION_RECENTS: { - openRecents(); - } return true; - case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { - expandStatusBar(); - } return true; + public boolean performGlobalAction(int action) throws RemoteException { + synchronized (mLock) { + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return false; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + switch (action) { + case AccessibilityService.GLOBAL_ACTION_BACK: { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); + } return true; + case AccessibilityService.GLOBAL_ACTION_HOME: { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); + } return true; + case AccessibilityService.GLOBAL_ACTION_RECENTS: { + openRecents(); + } return true; + case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { + expandStatusBar(); + } return true; + } + return false; + } finally { + Binder.restoreCallingIdentity(identity); } - return false; } public void onServiceDisconnected(ComponentName componentName) { @@ -1658,7 +1788,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // the state based on values in the settings database. if (mIsAutomation) { mUiAutomationService = null; - updateInternalStateLocked(); + recreateInternalStateLocked(getUserStateLocked(mUserId)); } } } @@ -1817,8 +1947,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get( - windowId); + AccessibilityConnectionWrapper wrapper = mGlobalInteractionConnections.get(windowId); + if (wrapper == null) { + wrapper = getCurrentUserStateLocked().mInteractionConnections.get(windowId); + } if (wrapper != null && wrapper.mConnection != null) { return wrapper.mConnection; } @@ -1828,7 +1960,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return null; } - private int resolveAccessibilityWindowId(int accessibilityWindowId) { + private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) { if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) { return mSecurityPolicy.mActiveWindowId; } @@ -1836,9 +1968,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } private float getCompatibilityScale(int windowId) { - IBinder windowToken = mWindowIdToWindowTokenMap.get(windowId); try { - return mWindowManager.getWindowCompatibilityScale(windowToken); + IBinder windowToken = mGlobalWindowTokens.get(windowId); + if (windowToken != null) { + return mWindowManagerService.getWindowCompatibilityScale(windowToken); + } + windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); + if (windowToken != null) { + return mWindowManagerService.getWindowCompatibilityScale(windowToken); + } } catch (RemoteException re) { /* ignore */ } @@ -1941,6 +2079,38 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + public int resolveCallingUserIdEnforcingPermissionsLocked(int userId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return mCurrentUserId; + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (callingUserId == userId) { + return userId; + } + if (!hasPermission(Manifest.permission.INTERACT_ACROSS_USERS) + && !hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { + throw new SecurityException("Call from user " + callingUserId + " as user " + + userId + " without permission INTERACT_ACROSS_USERS or " + + "INTERACT_ACROSS_USERS_FULL not allowed."); + } + if (userId == UserHandle.USER_CURRENT + || userId == UserHandle.USER_CURRENT_OR_SELF) { + return mCurrentUserId; + } + throw new IllegalArgumentException("Calling user can be changed to only " + + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); + } + + public boolean isCallerInteractingAcrossUsers(int userId) { + final int callingUid = Binder.getCallingUid(); + return (callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID + || userId == UserHandle.USER_CURRENT + || userId == UserHandle.USER_CURRENT_OR_SELF); + } + private boolean isRetrievalAllowingWindow(int windowId) { return (mActiveWindowId == windowId); } @@ -1953,22 +2123,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (OWN_PROCESS_ID == Binder.getCallingPid()) { return; } - final int permissionStatus = mContext.checkCallingPermission(permission); - if (permissionStatus != PackageManager.PERMISSION_GRANTED) { + if (hasPermission(permission)) { throw new SecurityException("You do not have " + permission + " required to call " + function); } } + private boolean hasPermission(String permission) { + return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; + } + private int getFocusedWindowId() { final long identity = Binder.clearCallingIdentity(); try { // We call this only on window focus change or after touch // exploration gesture end and the shown windows are not that // many, so the linear look up is just fine. - IBinder token = mWindowManager.getFocusedWindowToken(); + IBinder token = mWindowManagerService.getFocusedWindowToken(); if (token != null) { - SparseArray<IBinder> windows = mWindowIdToWindowTokenMap; + SparseArray<IBinder> windows = getCurrentUserStateLocked().mWindowTokens; final int windowCount = windows.size(); for (int i = 0; i < windowCount; i++) { if (windows.valueAt(i) == token) { @@ -1984,4 +2157,129 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return -1; } } + + private class UserState { + public final int mUserId; + + public final CopyOnWriteArrayList<Service> mServices = new CopyOnWriteArrayList<Service>(); + + public final RemoteCallbackList<IAccessibilityManagerClient> mClients = + new RemoteCallbackList<IAccessibilityManagerClient>(); + + public final Map<ComponentName, Service> mComponentNameToServiceMap = + new HashMap<ComponentName, Service>(); + + public final List<AccessibilityServiceInfo> mInstalledServices = + new ArrayList<AccessibilityServiceInfo>(); + + public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); + + public final Set<ComponentName> mTouchExplorationGrantedServices = + new HashSet<ComponentName>(); + + public final SparseArray<AccessibilityConnectionWrapper> + mInteractionConnections = + new SparseArray<AccessibilityConnectionWrapper>(); + + public final SparseArray<IBinder> mWindowTokens = new SparseArray<IBinder>(); + + public int mHandledFeedbackTypes = 0; + + public boolean mIsAccessibilityEnabled; + public boolean mIsTouchExplorationEnabled; + public boolean mIsDisplayMagnificationEnabled; + + public UserState(int userId) { + mUserId = userId; + } + } + + private final class AccessibilityContentObserver extends ContentObserver { + + private final Uri mAccessibilityEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_ENABLED); + + private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.TOUCH_EXPLORATION_ENABLED); + + private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + + private final Uri mEnabledAccessibilityServicesUri = Settings.Secure.getUriFor( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + + private final Uri mTouchExplorationGrantedAccessibilityServicesUri = Settings.Secure + .getUriFor(Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES); + + public AccessibilityContentObserver(Handler handler) { + super(handler); + } + + public void register(ContentResolver contentResolver) { + contentResolver.registerContentObserver(mAccessibilityEnabledUri, + false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver(mTouchExplorationEnabledUri, + false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, + false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, + false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mTouchExplorationGrantedAccessibilityServicesUri, + false, this, UserHandle.USER_ALL); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mAccessibilityEnabledUri.equals(uri)) { + synchronized (mLock) { + // We will update when the automation service dies. + if (mUiAutomationService == null) { + UserState userState = getCurrentUserStateLocked(); + handleAccessibilityEnabledSettingChangedLocked(userState); + updateInputFilterLocked(userState); + scheduleSendStateToClientsLocked(userState); + } + } + } else if (mTouchExplorationEnabledUri.equals(uri)) { + synchronized (mLock) { + // We will update when the automation service dies. + if (mUiAutomationService == null) { + UserState userState = getCurrentUserStateLocked(); + handleTouchExplorationEnabledSettingChangedLocked(userState); + updateInputFilterLocked(userState); + scheduleSendStateToClientsLocked(userState); + } + } + } else if (mDisplayMagnificationEnabledUri.equals(uri)) { + synchronized (mLock) { + // We will update when the automation service dies. + if (mUiAutomationService == null) { + UserState userState = getCurrentUserStateLocked(); + handleDisplayMagnificationEnabledSettingChangedLocked(userState); + updateInputFilterLocked(userState); + scheduleSendStateToClientsLocked(userState); + } + } + } else if (mEnabledAccessibilityServicesUri.equals(uri)) { + synchronized (mLock) { + // We will update when the automation service dies. + if (mUiAutomationService == null) { + UserState userState = getCurrentUserStateLocked(); + populateEnabledAccessibilityServicesLocked(userState); + manageServicesLocked(userState); + } + } + } else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) { + synchronized (mLock) { + // We will update when the automation service dies. + if (mUiAutomationService == null) { + UserState userState = getCurrentUserStateLocked(); + populateTouchExplorationGrantedAccessibilityServicesLocked(userState); + handleTouchExplorationGrantedAccessibilityServicesChangedLocked(userState); + } + } + } + } + } } diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index cb6b31a..c84f988 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -1150,20 +1150,9 @@ class TouchExplorer implements EventStreamTransformation { return; } - if (Build.IS_DEBUGGABLE) { - if (mSendHoverEnterDelayed.isPending()) { - throw new IllegalStateException("mSendHoverEnterDelayed must not be pending."); - } - if (mSendHoverExitDelayed.isPending()) { - throw new IllegalStateException("mSendHoverExitDelayed must not be pending."); - } - if (!mPerformLongPressDelayed.isPending()) { - throw new IllegalStateException( - "mPerformLongPressDelayed must not be pending."); - } - } - // Remove pending event deliveries. + mSendHoverEnterDelayed.remove(); + mSendHoverExitDelayed.remove(); mPerformLongPressDelayed.remove(); // The touch interaction has ended since we will send a click. diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java index 46bcc4a..fd9fc98 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java @@ -25,6 +25,7 @@ import android.os.IBinder; import android.os.Message; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; @@ -98,7 +99,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); // invoke the method under test - final int stateFlagsDisabled = mManagerService.addClient(mockClient); + final int stateFlagsDisabled = mManagerService.addClient(mockClient, UserHandle.USER_OWNER); boolean enabledAccessibilityDisabled = (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -110,7 +111,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { ensureAccessibilityEnabled(mContext, true); // invoke the method under test - final int stateFlagsEnabled = mManagerService.addClient(mockClient); + final int stateFlagsEnabled = mManagerService.addClient(mockClient, UserHandle.USER_OWNER); boolean enabledAccessibilityEnabled = (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -129,7 +130,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); // invoke the method under test - final int stateFlagsEnabled = mManagerService.addClient(mockClient); + final int stateFlagsEnabled = mManagerService.addClient(mockClient, UserHandle.USER_OWNER); boolean enabledAccessibilityEnabled = (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -141,7 +142,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { ensureAccessibilityEnabled(mContext, false); // invoke the method under test - final int stateFlagsDisabled = mManagerService.addClient(mockClient); + final int stateFlagsDisabled = mManagerService.addClient(mockClient, UserHandle.USER_OWNER); boolean enabledAccessibilityDisabled = (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -160,7 +161,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName(); // look for the two mock services - for (AccessibilityServiceInfo info : mManagerService.getInstalledAccessibilityServiceList()) { + for (AccessibilityServiceInfo info : mManagerService.getInstalledAccessibilityServiceList( + UserHandle.USER_OWNER)) { ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo; if (packageName.equals(serviceInfo.packageName)) { if (firstMockServiceClassName.equals(serviceInfo.name)) { @@ -201,7 +203,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { service.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(service); @@ -231,7 +233,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { service.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(service); @@ -261,7 +263,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { service.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(service); @@ -297,8 +299,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { service.replay(); // send the events - mManagerService.sendAccessibilityEvent(firstEvent); - mManagerService.sendAccessibilityEvent(secondEvent); + mManagerService.sendAccessibilityEvent(firstEvent, UserHandle.USER_OWNER); + mManagerService.sendAccessibilityEvent(secondEvent, UserHandle.USER_OWNER); // wait for #sendAccessibilityEvent to reach the backing service Thread.sleep(TIMEOUT_BINDER_CALL); @@ -354,7 +356,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { secondService.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(firstService); @@ -393,7 +395,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { secondService.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(firstService); @@ -434,7 +436,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { secondService.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(firstService); @@ -477,7 +479,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { secondService.replay(); // send the event - mManagerService.sendAccessibilityEvent(sentEvent); + mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(firstService); @@ -512,7 +514,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { secondService.replay(); // call the method under test - mManagerService.interrupt(); + mManagerService.interrupt(UserHandle.USER_OWNER); // verify if all expected methods have been called assertMockServiceVerifiedWithinTimeout(firstService); diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java index e083815..e7366ea 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java @@ -26,7 +26,7 @@ import static org.easymock.EasyMock.verify; import org.easymock.IArgumentMatcher; import android.accessibilityservice.AccessibilityServiceInfo; -import android.content.pm.ServiceInfo; +import android.os.UserHandle; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; @@ -70,14 +70,16 @@ public class AccessibilityManagerTest extends AndroidTestCase { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient(), + UserHandle.USER_OWNER)).andReturn( AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); - expect(mockServiceInterface.getInstalledAccessibilityServiceList()).andReturn( - expectedServices); + expect(mockServiceInterface.getInstalledAccessibilityServiceList(UserHandle.USER_OWNER)) + .andReturn(expectedServices); replay(mockServiceInterface); // invoke the method under test - AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface, + UserHandle.USER_OWNER); List<AccessibilityServiceInfo> receivedServices = manager.getInstalledAccessibilityServiceList(); @@ -92,13 +94,15 @@ public class AccessibilityManagerTest extends AndroidTestCase { public void testInterrupt() throws Exception { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( - AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); - mockServiceInterface.interrupt(); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient(), + UserHandle.USER_OWNER)).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); + mockServiceInterface.interrupt(UserHandle.USER_OWNER); replay(mockServiceInterface); // invoke the method under test - AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface, + UserHandle.USER_OWNER); manager.interrupt(); // verify the mock service was properly called @@ -109,12 +113,14 @@ public class AccessibilityManagerTest extends AndroidTestCase { public void testIsEnabled() throws Exception { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( - AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient(), + UserHandle.USER_OWNER)).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); replay(mockServiceInterface); // invoke the method under test - AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface, + UserHandle.USER_OWNER); boolean isEnabledServiceEnabled = manager.isEnabled(); // check expected result @@ -144,16 +150,18 @@ public class AccessibilityManagerTest extends AndroidTestCase { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( - AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); - expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) - .andReturn(true); - expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) - .andReturn(false); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient(), + UserHandle.USER_OWNER)).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent), + UserHandle.USER_OWNER)).andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent), + UserHandle.USER_OWNER)).andReturn(false); replay(mockServiceInterface); // invoke the method under test (manager and service in different processes) - AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface, + UserHandle.USER_OWNER); manager.sendAccessibilityEvent(sentEvent); // check expected result @@ -180,11 +188,13 @@ public class AccessibilityManagerTest extends AndroidTestCase { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(0); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient(), + UserHandle.USER_OWNER)).andReturn(0); replay(mockServiceInterface); // invoke the method under test (accessibility disabled) - AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface, + UserHandle.USER_OWNER); try { manager.sendAccessibilityEvent(sentEvent); fail("No accessibility events are sent if accessibility is disabled"); diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index cd7ee76..7b7a7b3 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -38,10 +38,8 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; @@ -216,6 +214,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + throw new UnsupportedOperationException(); + } + @Override public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { throw new UnsupportedOperationException(); @@ -223,7 +227,7 @@ public class MockPackageManager extends PackageManager { /** @hide */ @Override - public List<ResolveInfo> queryIntentActivitiesForUser(Intent intent, + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { throw new UnsupportedOperationException(); } @@ -255,6 +259,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { + throw new UnsupportedOperationException(); + } + @Override public ProviderInfo resolveContentProvider(String name, int flags) { throw new UnsupportedOperationException(); |