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

Commit c9bc9adf authored by Adam Bookatz's avatar Adam Bookatz
Browse files

UserManager.getProfileType hidden API and caching

Currently we have a few APIs for isXXXProfile. In the case of managed
profiles, we cachce the results.

Here, we introduce a new hidden API getProfileType, which gets and
caches the user type (string), if the user type is a profile. That way,
we automatically have the caching benefits for all profiles, not just
managed profiles.

We only do this for profiles because they have a relaxed permission
requirement. Other user types have strong restrictions, so we do not use
their more general getters here.

Test: atest UserManagerTest
Change-Id: I36b4caead83954518d6fc48860af61f8988f19a9
parent b2134e69
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -9642,7 +9642,7 @@ package android.os {
    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile();
    method public boolean isCloneProfile();
    method public boolean isCredentialSharedWithParent();
    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
+1 −3
Original line number Diff line number Diff line
@@ -107,9 +107,7 @@ interface IUserManager {
    void clearSeedAccountData(int userId);
    boolean someUserHasSeedAccount(in String accountName, in String accountType);
    boolean someUserHasAccount(in String accountName, in String accountType);
    boolean isProfile(int userId);
    boolean isManagedProfile(int userId);
    boolean isCloneProfile(int userId);
    String getProfileType(int userId);
    boolean isMediaSharedWithParent(int userId);
    boolean isCredentialSharedWithParent(int userId);
    boolean isDemoUser(int userId);
+77 −49
Original line number Diff line number Diff line
@@ -98,8 +98,8 @@ public class UserManager {
    /** The userId of the constructor param context. To be used instead of mContext.getUserId(). */
    private final @UserIdInt int mUserId;

    private Boolean mIsManagedProfileCached;
    private Boolean mIsProfileCached;
    /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
    private String mProfileTypeOfProcessUser = null;

    /**
     * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
@@ -2276,7 +2276,7 @@ public class UserManager {
     * {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}.
     * @hide
     */
    public static boolean isUserTypeManagedProfile(String userType) {
    public static boolean isUserTypeManagedProfile(@Nullable String userType) {
        return USER_TYPE_PROFILE_MANAGED.equals(userType);
    }

@@ -2284,7 +2284,7 @@ public class UserManager {
     * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}.
     * @hide
     */
    public static boolean isUserTypeGuest(String userType) {
    public static boolean isUserTypeGuest(@Nullable String userType) {
        return USER_TYPE_FULL_GUEST.equals(userType);
    }

@@ -2293,7 +2293,7 @@ public class UserManager {
     * {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}.
     * @hide
     */
    public static boolean isUserTypeRestricted(String userType) {
    public static boolean isUserTypeRestricted(@Nullable String userType) {
        return USER_TYPE_FULL_RESTRICTED.equals(userType);
    }

@@ -2301,7 +2301,7 @@ public class UserManager {
     * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}.
     * @hide
     */
    public static boolean isUserTypeDemo(String userType) {
    public static boolean isUserTypeDemo(@Nullable String userType) {
        return USER_TYPE_FULL_DEMO.equals(userType);
    }

@@ -2309,7 +2309,7 @@ public class UserManager {
     * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
     * @hide
     */
    public static boolean isUserTypeCloneProfile(String userType) {
    public static boolean isUserTypeCloneProfile(@Nullable String userType) {
        return USER_TYPE_PROFILE_CLONE.equals(userType);
    }

@@ -2525,25 +2525,50 @@ public class UserManager {
    }

    private boolean isProfile(@UserIdInt int userId) {
        if (userId == mUserId) {
        final String profileType = getProfileType(userId);
        return profileType != null && !profileType.equals("");
    }

    /**
     * Returns the user type of the context user if it is a profile.
     *
     * This is a more specific form of {@link #getUserType()} with relaxed permission requirements.
     *
     * @return the user type of the context user if it is a {@link #isProfile() profile},
     *         an empty string if it is not a profile,
     *         or null if the user doesn't exist.
     */
    @UserHandleAware(
            requiresAnyOfPermissionsIfNotCallerProfileGroup = {
                    android.Manifest.permission.MANAGE_USERS,
                    android.Manifest.permission.QUERY_USERS,
                    android.Manifest.permission.INTERACT_ACROSS_USERS})
    private @Nullable String getProfileType() {
        return getProfileType(mUserId);
    }

    /** @see #getProfileType() */
    private @Nullable String getProfileType(@UserIdInt int userId) {
        // First, the typical case (i.e. the *process* user, not necessarily the context user).
        // This cache cannot be become invalidated since it's about the calling process itself.
        if (userId == UserHandle.myUserId()) {
            // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
            // Worst case we might end up calling the AIDL method multiple times but that's fine.
            if (mIsProfileCached != null) {
                return mIsProfileCached;
            if (mProfileTypeOfProcessUser != null) {
                return mProfileTypeOfProcessUser;
            }
            try {
                mIsProfileCached = mService.isProfile(mUserId);
                return mIsProfileCached;
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
                final String profileType = mService.getProfileType(userId);
                if (profileType != null) {
                    return mProfileTypeOfProcessUser = profileType.intern();
                }
        } else {
            try {
                return mService.isProfile(userId);
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        }

        // The userId is not for the process's user. Use a slower cache that handles invalidation.
        return mProfileTypeCache.query(userId);
    }

    /**
@@ -2577,50 +2602,26 @@ public class UserManager {
            android.Manifest.permission.QUERY_USERS,
            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
    public boolean isManagedProfile(@UserIdInt int userId) {
        if (userId == mUserId) {
            // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
            // Worst case we might end up calling the AIDL method multiple times but that's fine.
            if (mIsManagedProfileCached != null) {
                return mIsManagedProfileCached;
            }
            try {
                mIsManagedProfileCached = mService.isManagedProfile(mUserId);
                return mIsManagedProfileCached;
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        } else {
            try {
                return mService.isManagedProfile(userId);
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        }
        return isUserTypeManagedProfile(getProfileType(userId));
    }

    /**
     * Checks if the context user is a clone profile.
     *
     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
     * must be in the same profile group of the user.
     *
     * @return whether the context user is a clone profile.
     *
     * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE
     * @hide
     */
    @SystemApi
    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
    @UserHandleAware
    @UserHandleAware(
            requiresAnyOfPermissionsIfNotCallerProfileGroup = {
                    android.Manifest.permission.MANAGE_USERS,
                    android.Manifest.permission.QUERY_USERS,
                    android.Manifest.permission.INTERACT_ACROSS_USERS})
    @SuppressAutoDoc
    public boolean isCloneProfile() {
        try {
            return mService.isCloneProfile(mUserId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
        return isUserTypeCloneProfile(getProfileType());
    }

    /**
@@ -5247,6 +5248,33 @@ public class UserManager {
        }
    }

    /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
    private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";

    private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache =
            new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) {
                @Override
                public String recompute(Integer query) {
                    try {
                        // Will be null (and not cached) if invalid user; otherwise cache the type.
                        String profileType = mService.getProfileType(query);
                        if (profileType != null) profileType = profileType.intern();
                        return profileType;
                    } catch (RemoteException re) {
                        throw re.rethrowFromSystemServer();
                    }
                }
                @Override
                public boolean bypass(Integer query) {
                    return query < 0;
                }
            };

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

    /**
     * @hide
     * User that enforces a restriction.
+12 −13
Original line number Diff line number Diff line
@@ -1517,7 +1517,6 @@ public class UserManagerService extends IUserManager.Stub {
        return userTypeDetails.getBadgeNoBackground();
    }

    @Override
    public boolean isProfile(@UserIdInt int userId) {
        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
        synchronized (mUsersLock) {
@@ -1526,21 +1525,19 @@ public class UserManagerService extends IUserManager.Stub {
        }
    }

    /**
     * Returns the user type (if it is a profile), empty string (if it isn't a profile),
     * or null (if the user doesn't exist).
     */
    @Override
    public boolean isManagedProfile(@UserIdInt int userId) {
        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isManagedProfile");
    public @Nullable String getProfileType(@UserIdInt int userId) {
        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getProfileType");
        synchronized (mUsersLock) {
            UserInfo userInfo = getUserInfoLU(userId);
            return userInfo != null && userInfo.isManagedProfile();
            if (userInfo != null) {
                return userInfo.isProfile() ? userInfo.userType : "";
            }
    }

    @Override
    public boolean isCloneProfile(@UserIdInt int userId) {
        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile");
        synchronized (mUsersLock) {
            UserInfo userInfo = getUserInfoLU(userId);
            return userInfo != null && userInfo.isCloneProfile();
            return null;
        }
    }

@@ -5163,6 +5160,8 @@ public class UserManagerService extends IUserManager.Stub {
                nextId = scanNextAvailableIdLocked();
            }
        }
        // If we got here, we probably recycled user ids, so invalidate any caches.
        UserManager.invalidateStaticUserProperties();
        if (nextId < 0) {
            throw new IllegalStateException("No user id available!");
        }
+2 −2
Original line number Diff line number Diff line
@@ -332,13 +332,13 @@ public final class UserTypeFactory {
                }

                String typeName = parser.getAttributeValue(null, "name");
                if (typeName == null) {
                if (typeName == null || typeName.equals("")) {
                    Slog.w(LOG_TAG, "Skipping user type with no name in "
                            + parser.getPositionDescription());
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
                typeName.intern();
                typeName = typeName.intern();

                UserTypeDetails.Builder builder;
                if (typeName.startsWith("android.")) {