Skip to content

受限用户

追踪 Android 17 受限用户的类型定义、父用户关系、默认限制、包初始化与生效路径。

基于android-17.0.0_r1
AndroidPMSUserManagerServiceMultiUserRestrictedUser

受限用户 ​

本文承接 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

java
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

java
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

java
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,符号:父用户字段写入

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 调用

java
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

java
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

java
@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

java
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,符号:用户限制检查

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 可创建性检查

java
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

java
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 独立”“不是,创建方法会追加硬编码限制”。