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
This commit is contained in:
Adam Bookatz
2022-02-04 17:11:25 -08:00
parent b2134e69f7
commit c9bc9adfd7
5 changed files with 93 additions and 68 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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.
*
* <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.

View File

@@ -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!");
}

View File

@@ -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.")) {