Skip to content

克隆应用

追踪 Android 17 Clone Profile 的类型属性、父用户关系、包状态共享边界和跨资料 Intent 过滤。

基于android-17.0.0_r1
AndroidPMSUserManagerServiceMultiUserCloneProfile

克隆应用 ​

本文承接 工作资料、多用户安装 和 per-user 包状态,聚焦 Android 17 的 Clone Profile(克隆资料/应用分身)。文章回答:克隆资料如何定义为 profile,如何由 UMS 创建并交给 PMS 初始化,以及“与父用户一起展示/共享媒体”为什么不等于共享 APK 数据或取消跨用户检查。

Clone Profile 的实现核心不是复制 APK,而是创建一个新的 profile userId。全局 PackageSetting 和代码路径仍可复用;目标 profile 有独立的 PackageUserState、应用数据和 UID。UserProperties 再描述展示、凭据、媒体、策略继承和跨 profile 解析行为。

1. 类型属性 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/UserTypeFactory.java,符号:getDefaultTypeProfileClone

java
private static UserTypeDetails.Builder
        getDefaultTypeProfileClone() {
    return new UserTypeDetails.Builder()
            .setName(USER_TYPE_PROFILE_CLONE)
            .setBaseType(FLAG_PROFILE)
            .setMaxAllowed(
                    getDefaultMaxAllowedSwitchableUsers())
            .setMaxAllowedPerParent(1)
            .setProfileParentRequired(true)
            .setDefaultRestrictions(null)
            .setDefaultCrossProfileIntentFilters(
                    getDefaultCloneCrossProfileIntentFilter())
            .setDefaultSecureSettings(
                    getDefaultNonManagedProfileSecureSettings())
            .setDefaultUserProperties(
                    new UserProperties.Builder()
                            .setStartWithParent(true)
                            .setShowInLauncher(
                                    UserProperties
                                            .SHOW_IN_LAUNCHER_WITH_PARENT)
                            .setShowInSettings(
                                    UserProperties
                                            .SHOW_IN_SETTINGS_WITH_PARENT)
                            .setInheritDevicePolicy(
                                    UserProperties
                                            .INHERIT_DEVICE_POLICY_FROM_PARENT)
                            .setUseParentsContacts(true)
                            .setMediaSharedWithParent(true)
                            .setCredentialShareableWithParent(true)
                            .setDeleteAppWithParent(true));
}

Clone Profile 是 FLAG_PROFILE,每个父用户最多一个,必须有 profile parent。源码还明确 defaultRestrictions(null),所以它与 Managed Profile 不同,不是通过一组默认 user restriction 建立隔离;克隆行为主要由 UserProperties 和 cross-profile filters 描述。

2. UserProperties ​

属性Clone 默认值含义
startWithParenttrue父用户启动时可一起启动
showInLauncherWITH_PARENT与父用户一起展示
showInSettingsWITH_PARENT设置界面跟随父用户展示
mediaSharedWithParenttrue媒体共享策略允许与父用户协作
credentialShareableWithParenttrue凭据可与父用户共享
inheritDevicePolicyFROM_PARENT继承设备策略属性
deleteAppWithParenttrue父用户删除应用时联动处理
crossProfileIntentResolutionStrategyNO_FILTERING使用 clone 专属解析策略字段

这些属性不是 PackageUserState 字段,也不会把两个 user 的应用数据合并。它们由 UMS/ActivityManager/Resolver 等消费者分别读取;PMS 仍然保留 per-user package state。

3. 创建入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:createProfileForUserWithThrow

java
public UserInfo createProfileForUserWithThrow(
        @Nullable String name,
        @NonNull String userType,
        @UserInfoFlag int flags,
        @UserIdInt int parentId,
        @Nullable String[] disallowedPackages)
        throws ServiceSpecificException {
    checkCreateUsersPermission(flags);
    try {
        return createUserInternal(name, userType,
                flags, parentId, disallowedPackages);
    } catch (UserManager.CheckedUserOperationException e) {
        throw e.toServiceSpecificException();
    }
}

系统或 DPC 通过 UMS 创建 clone profile,而不是让 PMS 直接新增一份 APK。UMS 会根据 USER_TYPE_PROFILE_CLONE 使用 DISALLOW_ADD_CLONE_PROFILE 限制,并检查 parent 和 per-parent 数量。

源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:createUserInternalUncheckedNoTracing

java
final UserTypeDetails details = mUserTypes.get(userType);
flags |= details.getDefaultUserInfoFlags();
final boolean isProfile = details.isProfile();

if (details.isProfileParentRequired()
        && !canAddMoreProfilesToUser(
                details, parentId)) {
    throwCheckedUserOperationException(
            "Cannot add more profiles of type "
                    + userType + " for user " + parentId,
            USER_OPERATION_ERROR_MAX_USERS);
}

userInfo = new UserInfo(userId, truncatedName,
        null, flags, userType);
userInfo.profileGroupId =
        parent.info.profileGroupId;

创建出的 clone 具有自己的 userId 和 UserInfo,但通过 profileGroupId 与父 profile group 关联。这个 group 关系供跨 profile 查询使用,不是数据目录共享句柄。

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> installablePackages =
        mSystemPackageInstaller
                .getInstallablePackagesForUserType(
                        userType);
mPm.createNewUser(userId, installablePackages,
        disallowedPackages);

Clone profile 的数据 key、DE 数据和包状态初始化与其他 profile 一样经过 UMS → PMS。PMS 的 Settings.createNewUserLI 决定哪些 system package 初始 installed;之后为 clone 启用已有 app 仍走 per-user 安装接口。

源码文件: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);

“克隆应用”不是在 Settings 中为每个 app 创建第二个 global PackageSetting。同一个 global package 只增加 clone user 的状态,代码路径和签名仍由全局记录管理。

5. 默认过滤器 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java,符号:clone filters

java
private static final DefaultCrossProfileIntentFilter
        CLONE_TO_PARENT_SEND_ACTION =
        new DefaultCrossProfileIntentFilter.Builder(
                Direction.TO_PARENT, 0x00000018,
                false /* letsPersonalDataIntoProfile */)
        .addAction(Intent.ACTION_SEND)
        .addAction(Intent.ACTION_SEND_MULTIPLE)
        .addAction(Intent.ACTION_SENDTO)
        .addDataType("*/*")
        .build();

private static final DefaultCrossProfileIntentFilter
        PARENT_TO_CLONE_SEND_ACTION =
        new DefaultCrossProfileIntentFilter.Builder(
                Direction.TO_PROFILE, 0x00000018,
                false /* letsPersonalDataIntoProfile */)
        .addAction(Intent.ACTION_SEND)
        .addAction(Intent.ACTION_SEND_MULTIPLE)
        .addAction(Intent.ACTION_SENDTO)
        .addDataType("*/*")
        .build();

Clone 的默认过滤器是双向、按 action/type 精确列出的规则。源码还定义了媒体捕获、HTTP/HTTPS view、联系人 pick、电话/SMS、Photo Picker 和 NFC 等不同过滤器;不能把 clone 的 NO_FILTERING 属性解释成“所有 Intent 无条件直通”。

源码文件:frameworks/base/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java,符号:getDefaultCloneProfileFilters

java
public static List<DefaultCrossProfileIntentFilter>
        getDefaultCloneProfileFilters() {
    List<DefaultCrossProfileIntentFilter> filters =
            new ArrayList<>(List.of(
                    PARENT_TO_CLONE_SEND_ACTION,
                    PARENT_TO_CLONE_WEB_VIEW_ACTION,
                    PARENT_TO_CLONE_PICK_INSERT_ACTION,
                    PARENT_TO_CLONE_DIAL_DATA,
                    CLONE_TO_PARENT_MEDIA_CAPTURE,
                    CLONE_TO_PARENT_SEND_ACTION,
                    CLONE_TO_PARENT_WEB_VIEW_ACTION,
                    CLONE_TO_PARENT_VIEW_ACTION,
                    CLONE_TO_PARENT_PICK_INSERT_ACTION,
                    CLONE_TO_PARENT_DIAL_DATA,
                    CLONE_TO_PARENT_SMS_MMS,
                    CLONE_TO_PARENT_PHOTOPICKER_SELECTION,
                    PARENT_TO_CLONE_NFC_TAG_DISCOVERED));
    filters.add(CLONE_TO_PARENT_PICK_CONTACTS_ACTION);
    return filters;
}

过滤器列表是 Clone Profile 的实际通信面。每条规则有 direction、flags、action、category 和 data type;解析时仍要结合 source/target user、组件 enabled 状态和包可见性。

6. 包启用与删除联动 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:installExistingPackageAsUser

java
if (!pkgSetting.getInstalled(userId)) {
    pkgSetting.setInstalled(true, userId);
    pkgSetting.setHidden(false, userId);
    pkgSetting.setInstallReason(installReason, userId);
    pkgSetting.setUninstallReason(
            PackageManager.UNINSTALL_REASON_UNKNOWN,
            userId);
    pkgSetting.setFirstInstallTime(
            System.currentTimeMillis(), userId);
    mPm.mSettings.writePackageRestrictionsLPr(userId);
    mPm.mSettings.writeKernelMappingLPr(pkgSetting);
    installed = true;
}

克隆 app 的第二份运行实例来自 clone user 的 installed state,而不是第二份 APK。父用户卸载时是否联动 clone,由 UserProperties.deleteAppWithParent 等消费者决定;PMS 的 per-user 状态仍需为两个 user 分别更新。

7. 共享属性 ​

“共享媒体”和“共享凭据”是 profile policy 属性,不是把两个用户的 app data 目录合并。clone app 仍以 clone user UID 运行,包权限、数据目录和 PackageUserState 仍按 userId 存储;共享行为由 MediaProvider、LockSettings、ActivityManager 等服务在各自 API 中实现。

“继承设备策略”同样不是把 DPC 的包状态复制到 clone。DPM/UMS 根据 profile group 和 user properties 决定策略作用范围,PMS 只在安装、可见性和组件查询处消费 user state。

8. 失败边界 ​

现象应检查
无法创建 cloneDISALLOW_ADD_CLONE_PROFILE、type enabled、parent 和 max per parent
clone 没有目标应用PMS 初始 installable 集合或 installExistingPackageAsUser
Intent 未跨资料clone filter 是否包含 action/type/direction,组件是否 enabled
Intent 过度直通resolver 是否错误忽略了 user/profile flags,而非只看 NO_FILTERING 属性
父用户删除 app 后 clone 仍在deleteAppWithParent 消费者与两个 user 的 installed state
clone 数据泄漏到父用户UID、CE/DE 目录、MediaProvider/LockSettings 共享策略边界

Clone Profile 的核心安全边界是“共享体验,不共享身份”:展示、媒体、凭据和策略可以协作,但应用进程、per-user 包状态和数据目录仍有独立 owner。

9. 阅读检查 ​

沿源码复述:createProfileForUserWithThrow(CLONE) → UMS 类型/parent/数量校验 → PMS createNewUser → clone 的 per-user package state → 默认 cross-profile filters → installExistingPackageAsUser 启用具体 app。

然后回答:Clone Profile 是否是第二份 APK?NO_FILTERING 是否允许所有 Intent 无条件跨越?mediaSharedWithParent=true 是否意味着 clone 与父用户共享 app data 目录?正确答案分别是“不是”“不是”“不是”。