From c9bc9adfd7da542e1275daecec3c965725fd6cd4 Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Fri, 4 Feb 2022 17:11:25 -0800 Subject: [PATCH] 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 --- core/api/system-current.txt | 2 +- core/java/android/os/IUserManager.aidl | 4 +- core/java/android/os/UserManager.java | 126 +++++++++++------- .../android/server/pm/UserManagerService.java | 25 ++-- .../android/server/pm/UserTypeFactory.java | 4 +- 5 files changed, 93 insertions(+), 68 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c3c53673aa159..0486d4c9b3a1c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -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); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index fcce266d6a2d4..39ca596f9181e 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -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); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c597a1a6e7dc3..886c3fcb05f5f 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -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(); - } - } else { - try { - return mService.isProfile(userId); + final String profileType = mService.getProfileType(userId); + if (profileType != null) { + return mProfileTypeOfProcessUser = profileType.intern(); + } } 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. * - *

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 mProfileTypeCache = + new PropertyInvalidatedCache(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. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index e63d72116bb5a..5c4d0116e1a04 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -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(); - } - } - - @Override - public boolean isCloneProfile(@UserIdInt int userId) { - checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile"); - synchronized (mUsersLock) { - UserInfo userInfo = getUserInfoLU(userId); - return userInfo != null && userInfo.isCloneProfile(); + if (userInfo != null) { + return userInfo.isProfile() ? userInfo.userType : ""; + } + 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!"); } diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 6e6585ebcf148..1e3b67c394cbe 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -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.")) {