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

Commit ebe2a0ba authored by Lee Shombert's avatar Lee Shombert
Browse files

Binder cache for isUserUnlocked().

Bug: 140788621

Test: A special build that puts the PropertyInvalidatedCache in
verification mode was loaded on the device.  Then one iteration of MPTS
was executed.  No cache inconsistencies were found and no SELinux
violations (associated with the binder cache) were found.  The number of
cache misses was approximately 10% of the total binder calls.  All
binder calls went through the cache.  Added a new user and later deleted
the same user; verified that caches were invalidated as expected and no
errors were detected by the PropertyInvalidatedCache verification mode.

Passed the CtsMultiUserHostTestCases test.

Change-Id: I32e3e39768e288101c82b02e63a7eb20d5ed7f93
parent 0a5f568a
Loading
Loading
Loading
Loading
+28 −5
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -2150,16 +2151,38 @@ public class UserManager {
        return isUserUnlocked(user.getIdentifier());
    }

    private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
            "cache_key.is_user_unlocked";

    private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
            new PropertyInvalidatedCache<Integer, Boolean>(
                32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
                @Override
                protected Boolean recompute(Integer query) {
                    try {
                        return mService.isUserUnlocked(query);
                    } catch (RemoteException re) {
                        throw re.rethrowFromSystemServer();
                    }
                }
            };

    /** {@hide} */
    @UnsupportedAppUsage
    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
    public boolean isUserUnlocked(@UserIdInt int userId) {
        try {
            return mService.isUserUnlocked(userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        return mIsUserUnlockedCache.query(userId);
    }

    /** {@hide} */
    public void disableIsUserUnlockedCache() {
        mIsUserUnlockedCache.disableLocal();
    }

    /** {@hide} */
    public static final void invalidateIsUserUnlockedCache() {
        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
    }

    /**
+39 −5
Original line number Diff line number Diff line
@@ -342,9 +342,43 @@ class StorageManagerService extends IStorageManager.Stub
     */
    private final Object mPackagesLock = new Object();

    /**
     * mLocalUnlockedUsers affects the return value of isUserUnlocked.  If
     * any value in the array changes, then the binder cache for
     * isUserUnlocked must be invalidated.  When adding mutating methods to
     * WatchedLockedUsers, be sure to invalidate the cache in the new
     * methods.
     */
    private class WatchedLockedUsers {
        private int[] users = EmptyArray.INT;
        public WatchedLockedUsers() {
        }
        public void append(int userId) {
            users = ArrayUtils.appendInt(users, userId);
            invalidateIsUserUnlockedCache();
        }
        public void remove(int userId) {
            users = ArrayUtils.removeInt(users, userId);
            invalidateIsUserUnlockedCache();
        }
        public boolean contains(int userId) {
            return ArrayUtils.contains(users, userId);
        }
        public int[] all() {
            return users;
        }
        @Override
        public String toString() {
            return Arrays.toString(users);
        }
        private void invalidateIsUserUnlockedCache() {
            UserManager.invalidateIsUserUnlockedCache();
        }
    }

    /** Set of users that we know are unlocked. */
    @GuardedBy("mLock")
    private int[] mLocalUnlockedUsers = EmptyArray.INT;
    private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
    /** Set of users that system knows are unlocked. */
    @GuardedBy("mLock")
    private int[] mSystemUnlockedUsers = EmptyArray.INT;
@@ -3042,7 +3076,7 @@ class StorageManagerService extends IStorageManager.Stub
        }

        synchronized (mLock) {
            mLocalUnlockedUsers = ArrayUtils.appendInt(mLocalUnlockedUsers, userId);
            mLocalUnlockedUsers.append(userId);
        }
    }

@@ -3058,14 +3092,14 @@ class StorageManagerService extends IStorageManager.Stub
        }

        synchronized (mLock) {
            mLocalUnlockedUsers = ArrayUtils.removeInt(mLocalUnlockedUsers, userId);
            mLocalUnlockedUsers.remove(userId);
        }
    }

    @Override
    public boolean isUserKeyUnlocked(int userId) {
        synchronized (mLock) {
            return ArrayUtils.contains(mLocalUnlockedUsers, userId);
            return mLocalUnlockedUsers.contains(userId);
        }
    }

@@ -4177,7 +4211,7 @@ class StorageManagerService extends IStorageManager.Stub
            }

            pw.println();
            pw.println("Local unlocked users: " + Arrays.toString(mLocalUnlockedUsers));
            pw.println("Local unlocked users: " + mLocalUnlockedUsers);
            pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));

            final ContentResolver cr = mContext.getContentResolver();
+40 −1
Original line number Diff line number Diff line
@@ -462,8 +462,42 @@ public class UserManagerService extends IUserManager.Stub {
    @GuardedBy("mUsersLock")
    private boolean mForceEphemeralUsers;

    /**
     * The member mUserStates affects the return value of isUserUnlocked.
     * If any value in mUserStates changes, then the binder cache for
     * isUserUnlocked must be invalidated.  When adding mutating methods to
     * WatchedUserStates, be sure to invalidate the cache in the new
     * methods.
     */
    private class WatchedUserStates {
        final SparseIntArray states;
        public WatchedUserStates() {
            states = new SparseIntArray();
        }
        public int get(int userId) {
            return states.get(userId);
        }
        public int get(int userId, int fallback) {
            return states.indexOfKey(userId) >= 0 ? states.get(userId) : fallback;
        }
        public void put(int userId, int state) {
            states.put(userId, state);
            invalidateIsUserUnlockedCache();
        }
        public void delete(int userId) {
            states.delete(userId);
            invalidateIsUserUnlockedCache();
        }
        @Override
        public String toString() {
            return states.toString();
        }
        private void invalidateIsUserUnlockedCache() {
            UserManager.invalidateIsUserUnlockedCache();
        }
    }
    @GuardedBy("mUserStates")
    private final SparseIntArray mUserStates = new SparseIntArray();
    private final WatchedUserStates mUserStates = new WatchedUserStates();

    private static UserManagerService sInstance;

@@ -4801,6 +4835,11 @@ public class UserManagerService extends IUserManager.Stub {
                    || (state == UserState.STATE_RUNNING_UNLOCKED);
        }

        /**
         * The return values of this method are cached in clients.  If the
         * logic in this function changes then the cache invalidation code
         * may need to be revisited.
         */
        @Override
        public boolean isUserUnlocked(@UserIdInt int userId) {
            int state;