受限用户
本文承接 UMS 与 PMS、per-user 包状态 和 工作资料。文章只讨论 USER_TYPE_FULL_RESTRICTED 的设备端源码:它怎样被创建、怎样关联父用户、哪些限制在创建后写入、PMS 如何据此限制安装和包状态。它不把 Settings UI 或 DPC 的产品策略当作 UMS 的实现。
受限用户与 Managed Profile 的关键差异是类型层级:受限用户是 FLAG_FULL,拥有独立的完整用户空间;它可以带一个“应用来源父用户”,但不是 FLAG_PROFILE,因此不使用 profileGroupId 作为 profile 组关系,而使用 restrictedProfileParentId。
1. 类型定义
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserTypeFactory.java,符号:getDefaultTypeFullRestricted
private static UserTypeDetails.Builder
getDefaultTypeFullRestricted() {
// UI 是否提供创建入口由 Settings 资源决定。
return new UserTypeDetails.Builder()
.setName(USER_TYPE_FULL_RESTRICTED)
.setBaseType(FLAG_FULL)
.setDefaultUserInfoPropertyFlags(
FLAG_RESTRICTED)
.setMaxAllowed(
getDefaultMaxAllowedForSwitchableTypes())
.setProfileParentRequired(false)
.setDefaultRestrictions(null);
}源码注释明确指出:受限用户的硬编码限制由 UserManagerService.createRestrictedProfile() 追加,而不是由 UserTypeDetails.defaultRestrictions 提供。profileParentRequired=false 也很重要:受限用户可以从带 parent 参数的入口创建,但类型本身不是普通 profile。
2. 创建入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:createRestrictedProfileWithThrow
public UserInfo createRestrictedProfileWithThrow(
@Nullable String name, int parentUserId)
throws ServiceSpecificException {
checkCreateUsersPermission("setupRestrictedProfile");
UserInfo user = createProfileForUserWithThrow(
name, USER_TYPE_FULL_RESTRICTED, 0,
parentUserId, null);
final long identity = Binder.clearCallingIdentity();
try {
setUserRestriction(
UserManager.DISALLOW_MODIFY_ACCOUNTS,
true, user.id);
Settings.Secure.putIntForUser(
mContext.getContentResolver(),
Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_OFF,
user.id);
setUserRestriction(
UserManager.DISALLOW_SHARE_LOCATION,
true, user.id);
} finally {
Binder.restoreCallingIdentity(identity);
}
return user;
}受限用户的创建分成两段:先走通用 profile/user 创建流程,再追加 DISALLOW_MODIFY_ACCOUNTS、关闭 location mode、追加 DISALLOW_SHARE_LOCATION。这解释了为什么类型定义中的 default restrictions 为空,但新建完成后用户仍然具有受限行为。
3. 父用户关系
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:createUserInternalUncheckedNoTracing
final boolean isProfile = userTypeDetails.isProfile();
final boolean isRestricted =
UserManager.isUserTypeRestricted(userType);
final boolean requiresProfileParent =
userTypeDetails.isProfileParentRequired();
if (requiresProfileParent
&& !canAddMoreProfilesToUser(
userTypeDetails, parentId)) {
throwCheckedUserOperationException(
"Cannot add more profiles of type "
+ userType + " for user " + parentId,
USER_OPERATION_ERROR_MAX_USERS);
}
if (isRestricted
&& !canHaveRestrictedProfileNoChecks(parentId)) {
throwCheckedUserOperationException(
"Cannot add restricted profile for user "
+ parentId,
USER_OPERATION_ERROR_UNKNOWN);
}受限用户的可创建性使用 canHaveRestrictedProfileNoChecks,与普通 profile 的 canAddMoreProfilesToUser 分开。它反映了“完整用户 + 受限应用来源”的特殊关系。
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:父用户字段写入
if (parent != null) {
if (isProfile) {
userInfo.profileGroupId =
parent.info.profileGroupId;
} else if (isRestricted) {
if (parent.info.restrictedProfileParentId
== UserInfo.NO_PROFILE_GROUP_ID) {
parent.info.restrictedProfileParentId =
parent.info.id;
writeUserLP(parent);
}
userInfo.restrictedProfileParentId =
parent.info.restrictedProfileParentId;
}
}受限用户把父用户的 restrictedProfileParentId 作为来源关系;Managed Profile 则使用 profileGroupId。两个字段不能互换,否则跨用户可见性和“从父用户复制可用应用”的逻辑会错位。
4. PMS 初始化
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:创建流程中的 PMS 调用
getStorageManagerInternal().createUserStorageKeys(
userId, userInfo.isEphemeral());
mUserDataPreparer.prepareUserData(
userInfo, StorageManager.FLAG_STORAGE_DE);
getLockSettingsInternal().createNewUser(
userId, userInfo.serialNumber);
Set<String> installable =
mSystemPackageInstaller
.getInstallablePackagesForUserType(userType);
mPm.createNewUser(userId, installable,
disallowedPackages);受限用户和普通用户一样先准备 DE,再由 PMS 为该 user 写入包状态和数据目录。installable 集合的来源是用户类型安装策略;受限行为本身主要由 restrictions 和后续 per-user 状态控制,并非通过复制父用户的 PackageSetting 完成。
源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java,符号:createNewUserLI
boolean shouldMaybeInstall = ps.isSystem()
&& !ArrayUtils.contains(disallowedPackages,
ps.getPackageName())
&& !ps.getPkgState().isHiddenUntilInstalled();
boolean shouldReallyInstall = shouldMaybeInstall
&& (userTypeInstallablePackages == null
|| userTypeInstallablePackages.contains(
ps.getPackageName()));
ps.setInstalled(shouldReallyInstall, userHandle);
ps.setStopped(shouldBeStopped, userHandle);PMS 只为符合 system/allowlist/denylist 条件的包设置 installed=true。受限用户的独立空间不代表自动拥有父用户全部应用;父用户已安装只是候选信息,真正初始状态仍由 Settings 的用户类型集合决定。
5. 限制集合
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:限制字段与 computeEffectiveUserRestrictionsLR
@GuardedBy("mRestrictionsLock")
private final RestrictionsSet mBaseUserRestrictions =
new RestrictionsSet();
@GuardedBy("mRestrictionsLock")
private final RestrictionsSet mDevicePolicyUserRestrictions =
new RestrictionsSet();
private Bundle computeEffectiveUserRestrictionsLR(
int userId) {
Bundle base = mBaseUserRestrictions
.getRestrictionsNonNull(userId);
Bundle global = mDevicePolicyUserRestrictions
.getRestrictionsNonNull(UserHandle.USER_ALL);
Bundle local = mDevicePolicyUserRestrictions
.getRestrictionsNonNull(userId);
Bundle result = new Bundle(base);
UserRestrictionsUtils.merge(result, global);
UserRestrictionsUtils.merge(result, local);
return result;
}受限用户的限制最终也进入 effective restrictions:基础限制、设备策略全局限制、设备策略本地限制按 userId 合并。mCachedEffectiveUserRestrictions 只是缓存,真正的 owner 是三类输入集合和 computeEffectiveUserRestrictionsLR。
6. 限制生效
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:setUserRestrictionInner、applyUserRestrictionsLR
void setUserRestrictionInner(int userId,
String key, boolean value) {
synchronized (mRestrictionsLock) {
Bundle current = new Bundle(
mBaseUserRestrictions
.getRestrictionsNonNull(userId));
if (value) {
current.putBoolean(key, true);
} else {
current.remove(key);
}
if (mBaseUserRestrictions.updateRestrictions(
userId, current)) {
applyUserRestrictionsLR(userId);
}
}
}setUserRestriction 修改的是 base restrictions;随后 applyUserRestrictionsLR 将 effective 结果传播给 AppOps、ActivityManager 等消费者。受限用户创建时追加的限制并不是静态注释,而是会进入这条生效和通知链。
7. PMS 消费限制
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:用户限制检查
if (mPm.isUserRestricted(userId,
UserManager.DISALLOW_INSTALL_APPS)) {
return Pair.create(
PackageManager.INSTALL_FAILED_USER_RESTRICTED,
intentSender);
}像 installExistingPackageAsUser 这样的 PMS 安装入口,会在修改 PackageUserState 前检查 DISALLOW_INSTALL_APPS。并非所有受限限制都由 PMS 消费;账号、位置、蓝牙等限制由对应系统服务检查。阅读受限用户源码时要把“限制的 owner”和“限制的消费者”分开。
8. 父用户应用来源
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:受限 profile 可创建性检查
private boolean canHaveRestrictedProfileNoChecks(
int parentId) {
UserInfo parent = getUserInfoLU(parentId);
if (parent == null) {
return false;
}
if (parent.isRestricted() || parent.isGuest()) {
return false;
}
return parent.isAdmin() || parent.id == UserHandle.USER_SYSTEM;
}受限用户不是任意用户的子用户;父用户必须满足 UMS 的资格检查。父用户关系用于创建约束和应用来源语义,不会让受限用户共享父用户的 UID 或数据目录。
图中“创建成功”与“安装允许”是两个判断:前者由 UMS 的用户类型/父用户检查决定,后者由 effective restriction 在 PMS 入口消费决定。
9. 生命周期与删除
受限用户删除走通用 UMS 生命周期:先标记 removing/disabled,等待 ActivityManager 停止用户和 ACTION_USER_REMOVED 广播,再调用 mPm.cleanUpUser 清理 per-user 包状态、权限、AppsFilter、InstallerService,最后销毁 CE/DE 数据。受限用户没有一套独立的 PMS 删除实现。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:cleanUpUser
mSettings.removeUserLPw(userId);
mAppsFilter.onUserDeleted(
snapshotComputer(), userId);
mPermissionManager.onUserRemoved(userId);
mInstallerService.onUserRemoved(userId);
mInstantAppRegistry.onUserRemoved(userId);删除 user state 后,UMS 再由 UserDataPreparer.destroyUserData 清空用户数据目录。包状态删除和目录销毁属于不同 owner,不能只检查其中一边。
10. 失败边界
| 现象 | 真实检查 |
|---|---|
| 无法创建受限用户 | DISALLOW_ADD_USER、parent 资格、用户数量/磁盘检查 |
| 创建成功但包不可安装 | DISALLOW_INSTALL_APPS 或 Settings 初始 allowlist |
| 限制看似未生效 | base/device-policy restrictions 是否重新计算并 apply |
| 父用户关系异常 | restrictedProfileParentId 是否写入,而非 profileGroupId |
| 删除后仍可见 | UMS 删除广播、PMS cleanUpUser、package list 持久化 |
| 目录残留 | UserDataPreparer.destroyUserData、storage key 和 serial 调和 |
受限用户的核心边界是“限制状态”和“包状态”分离:限制决定哪些操作被拒绝,PackageUserState 决定包在该用户是否 installed/enabled,数据目录和 UID 决定运行时隔离。修改其中一层不会自动替代另外两层。
11. 阅读检查
沿源码复述这条路径:createRestrictedProfileWithThrow → 通用用户创建 → restrictedProfileParentId → PMS createNewUser → 追加账号/位置限制 → effective restrictions apply → PMS 安装入口检查 DISALLOW_INSTALL_APPS。
然后判断:受限用户是 FLAG_PROFILE 吗?它与父用户共享 PackageUserState 吗?defaultRestrictions=null 是否意味着没有限制?正确答案分别是“不是,是 FLAG_FULL”“不共享,每个 user 独立”“不是,创建方法会追加硬编码限制”。
