Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58d37b55 authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

Multi-user support for the accessibility layer.

1. This change converts the accessibility manager service to
   maintain a state per user. When the user changes the services
   for the user that is going away are disconnected, the local
   accessibility managers in the processes for this user are
   disabled, the state is swapped with the new user's one, and
   the new user state is refreshed.

   This change updates all calls into the system to use their
   user specific versions when applicable. For example, regisetring
   content observers, package monitors, calls into other system
   services, etc.

   There are some components that are shared across users such
   as UI created by the system process and the SystemUI package.
   Such components are managed as a global state shared across
   all users and are updated accordingly on a user switch. Since
   the SystemUI is running in a normal app process this change
   adds hidden APIs on the local window manager to allow the
   SystemUI to notify the accessibility layer that it will run
   accross users.

   Calls to AccessibiltyManager's isEnabled(), isTouchExplorationEnabled()
   and sendAccessibilityEvent return false or a are a nop for a
   background user sice he should not send accessibility events,
   and should not perform touch exploration.

   Update the internal accessibility tests due to changes in the
   AccessibilityManager.

   This change also fixes several issues that were encountered
   such as calling out the accessibility manager service with a
   lock held.

   Removed some incorrect debugging code from the TouchExplorer
   that was leading to a system crash.

bug:6967373

Change-Id: I2cf32ffdee1d827a8197ae4ce717dc0ff798b259
parent 059aedf8
Loading
Loading
Loading
Loading
+16 −7
Original line number Diff line number Diff line
@@ -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,18 +555,23 @@ 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) {
+55 −1
Original line number Diff line number Diff line
@@ -1796,6 +1796,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.
     *
@@ -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);


@@ -1943,6 +1976,27 @@ public abstract class PackageManager {
    public abstract List<ResolveInfo> queryIntentServices(Intent intent,
            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&lt;ResolveInfo&gt; 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.
     *
+50 −10
Original line number Diff line number Diff line
@@ -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;
@@ -128,36 +131,73 @@ 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);
        }
+6 −6
Original line number Diff line number Diff line
@@ -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);

+5 −0
Original line number Diff line number Diff line
@@ -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 {
Loading