Skip to content

跨 Profile Intent

分析跨 Profile Intent 过滤器、跳过当前用户、链式解析与转发结果生成。

AndroidPMSIntentCrossProfile

跨 Profile Intent ​

跨 Profile Intent 不是把一个 Intent 直接复制到另一个 user。Android 17 先在源 user 的 CrossProfileIntentResolver 中匹配过滤器,再由 CrossProfileIntentResolverEngine 选择目标 user 的 resolver 策略,生成 IntentForwarderActivity 的 ResolveInfo,最后与当前 profile、Web 域名批准和 Instant App 结果合并。

1. 核心对象 ​

对象作用
CrossProfileIntentFilter源 user 到目标 user 的匹配规则
CrossProfileIntentResolver保存某个 user 的跨 profile filters
CrossProfileIntentResolverEngineDFS 遍历目标 profile 并合并结果
CrossProfileResolver某一对 user 的具体解析策略
IntentForwarderActivity作为候选结果的系统转发组件

2. Filter 数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java

java
final int mTargetUserId;
final String mOwnerPackage;
final int mFlags;
final int mAccessControlLevel;

public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010;

public static final int ACCESS_LEVEL_ALL = 0;
public static final int ACCESS_LEVEL_SYSTEM = 10;
public static final int ACCESS_LEVEL_SYSTEM_ADD_ONLY = 20;

过滤器保存目标 user、owner package、控制 flags 和修改权限级别。SKIP_CURRENT_PROFILE、ONLY_IF_NO_MATCH_FOUND 是 PackageManager 公开的路由 flags;FLAG_ALLOW_CHAINED_RESOLUTION 控制是否允许目标 profile 继续向下递归。

3. 添加权限边界 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
public void addCrossProfileIntentFilter(
        @NonNull Computer snapshot, WatchedIntentFilter intentFilter,
        String ownerPackage, int sourceUserId, int targetUserId,
        int flags) {
    mContext.enforceCallingOrSelfPermission(
            Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
    final int callingUid = Binder.getCallingUid();
    enforceOwnerRights(snapshot, ownerPackage, callingUid);
    mUserManager.enforceCrossProfileIntentFilterAccess(
            sourceUserId, targetUserId, callingUid, true);
    if (!intentFilter.checkDataPathAndSchemeSpecificParts()) {
        throw new IllegalArgumentException(
                "Invalid intent data paths or scheme specific parts");
    }
    if (intentFilter.countActions() == 0) {
        Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
        return;
    }

添加过滤器需要跨用户权限、owner package 归属和 UserManager 的 profile 访问检查;非法 path/SSP 或无 action 的 filter 不会进入 resolver。跨 profile 路由因此不是普通应用可以随意写入的全局表。

java
synchronized (mLock) {
    CrossProfileIntentFilter newFilter =
            new CrossProfileIntentFilter(
                    intentFilter, ownerPackage, targetUserId,
                    flags, mUserManager
                            .getCrossProfileIntentFilterAccessControl(
                                    sourceUserId, targetUserId));
    CrossProfileIntentResolver resolver =
            mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
    ArrayList<CrossProfileIntentFilter> existing =
            resolver.findFilters(intentFilter);
    if (existing != null) {
        for (CrossProfileIntentFilter old : existing) {
            if (newFilter.equalsIgnoreFilter(old)) return;
        }
    }
    resolver.addFilter(snapshotComputer(), newFilter);
}
scheduleWritePackageRestrictions(sourceUserId);

相同 IntentFilter 且元数据相同的规则不会重复添加。修改在锁内完成,随后写入 source user 的 package restrictions。

4. 递归解析 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java

java
public List<CrossProfileDomainInfo> resolveIntent(
        @NonNull Computer computer, Intent intent,
        String resolvedType, int userId, long flags,
        String pkgName, boolean hasNonNegativePriorityResult,
        boolean resolveForStart,
        Function<String, PackageStateInternal> pkgSettingFunction) {
    return resolveIntentInternal(
            computer, intent, resolvedType, userId, userId,
            flags, pkgName, hasNonNegativePriorityResult,
            resolveForStart, pkgSettingFunction, null);
}

公开入口把 source user 和当前遍历 user 初始化为同一个值,内部递归再改变当前 user。visitedUserIds 防止 profile 图中出现环。

java
List<CrossProfileIntentFilter> matchingFilters =
        computer.getMatchingCrossProfileIntentFilters(
                intent, resolvedType, userId);
if (matchingFilters == null || matchingFilters.isEmpty()) {
    if (sourceUserId == userId && intent.hasWebURI()) {
        UserInfo parent = computer.getProfileParent(userId);
        if (parent != null) {
            CrossProfileDomainInfo info =
                    computer.getCrossProfileDomainPreferredLpr(
                            intent, resolvedType, flags,
                            userId, parent.id);
            if (info != null) crossProfileDomainInfos.add(info);
        }
    }
    return crossProfileDomainInfos;
}

没有匹配的跨 profile filter 时,普通 Intent 结束;Web Intent 仍可能通过 parent profile 的域名批准获得 generalized forwarding result。

5. 分组与链式解析 ​

java
SparseArray<List<CrossProfileIntentFilter>> byUser = new SparseArray<>();
for (CrossProfileIntentFilter filter : matchingFilters) {
    byUser.computeIfAbsent(
            filter.mTargetUserId, key -> new ArrayList<>()).add(filter);
}
if (visitedUserIds == null) {
    visitedUserIds = new HashSet<>();
    visitedUserIds.add(userId);
}
for (int index = 0; index < byUser.size(); index++) {
    int targetUserId = byUser.keyAt(index);
    if (visitedUserIds.contains(targetUserId)) continue;
    CrossProfileResolver resolver = chooseCrossProfileResolver(
            computer, userId, targetUserId,
            resolveForStart, flags);
    if (resolver == null) continue;

多个 filter 指向同一目标 user 时先分组,再为 user pair 选择 resolver 策略。访问过的 user 不再重复解析。

java
List<CrossProfileDomainInfo> infos = resolver.resolveIntent(
        computer, intent, resolvedType, userId, targetUserId,
        flags, pkgName, byUser.valueAt(index),
        hasNonNegativePriorityResult, pkgSettingFunction);
crossProfileDomainInfos.addAll(infos);
visitedUserIds.add(targetUserId);

boolean allowChainedResolution = false;
for (CrossProfileIntentFilter filter : byUser.valueAt(index)) {
    if ((filter.mFlags
            & CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION) != 0) {
        allowChainedResolution = true;
        break;
    }
}
if (allowChainedResolution) {
    crossProfileDomainInfos.addAll(resolveIntentInternal(
            computer, intent, resolvedType, sourceUserId,
            targetUserId, flags, pkgName,
            hasNonNegativePriority(infos), resolveForStart,
            pkgSettingFunction, visitedUserIds));
}

只有 filter 明确允许 chained resolution,目标 user 才会作为新的当前 user 继续 DFS。递归时用目标 user 的结果重新计算 priority 条件。

6. 跳过当前 Profile ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java

java
public boolean shouldSkipCurrentProfile(
        Computer computer, Intent intent,
        String resolvedType, int sourceUserId) {
    List<CrossProfileIntentFilter> matches =
            computer.getMatchingCrossProfileIntentFilters(
                    intent, resolvedType, sourceUserId);
    if (matches != null) {
        for (CrossProfileIntentFilter filter : matches) {
            if ((filter.getFlags()
                    & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
                return true;
            }
        }
    }
    return false;
}

ComputerEngine.queryIntentActivitiesInternalBody 在查询当前 Activity resolver 前调用此方法。只要匹配到一个 SKIP_CURRENT_PROFILE filter,当前 profile 的候选全部跳过,只保留转发结果。

7. 默认策略 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java

java
public List<CrossProfileDomainInfo> resolveIntent(
        Computer computer, Intent intent, String resolvedType,
        int userId, int targetUserId, long flags,
        String pkgName, List<CrossProfileIntentFilter> filters,
        boolean hasNonNegativePriorityResult,
        Function<String, PackageStateInternal> pkgSettingFunction) {
    List<CrossProfileDomainInfo> result = new ArrayList<>();
    if (pkgName != null) return result;
    CrossProfileDomainInfo skip = querySkipCurrentProfileIntents(
            computer, filters, intent, resolvedType,
            flags, userId, pkgSettingFunction);
    if (skip != null) {
        result.add(skip);
        return filterIfNotSystemUser(result, userId);
    }
    CrossProfileDomainInfo specific = queryCrossProfileIntents(
            computer, filters, intent, resolvedType,
            flags, userId, hasNonNegativePriorityResult,
            pkgSettingFunction);
    if (specific != null) result.add(specific);
    return result;
}

指定 package 时不执行跨 profile 默认策略。策略先处理 skip filter,再处理普通 filter;最终还会过滤不属于系统 user 的组件。

ONLY_IF_NO_MATCH_FOUND 的语义在普通 filter 查询中生效:当源 profile 已有非负 priority 结果时,低优先级跨 profile 转发不会加入结果。它不是“源 profile 完全没有组件”才转发,而是没有可接受 priority 的当前结果才转发。

8. 转发 ResolveInfo ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java

java
public final ResolveInfo createForwardingResolveInfoUnchecked(
        WatchedIntentFilter filter, int sourceUserId,
        int targetUserId) {
    ResolveInfo result = new ResolveInfo();
    boolean targetIsProfile = mUserManager
            .getUserInfo(targetUserId).isManagedProfile();
    String className = targetIsProfile
            ? FORWARD_INTENT_TO_MANAGED_PROFILE
            : FORWARD_INTENT_TO_PARENT;
    ComponentName component = new ComponentName(
            androidApplication().packageName, className);
    ActivityInfo ai = getActivityInfoCrossProfile(
            component, 0, sourceUserId);
    if (!targetIsProfile) {
        ai.showUserIcon = targetUserId;
        result.noResourceId = true;
    }
    result.activityInfo = ai;
    result.priority = 0;
    result.preferredOrder = 0;
    result.match = 0;
    result.isDefault = true;
    result.filter = new IntentFilter(filter.getIntentFilter());
    result.targetUserId = targetUserId;
    result.userHandle = UserHandle.of(sourceUserId);
    return result;
}

转发结果的组件不是目标 profile 中的业务 Activity,而是 android 包内的 forwarding Activity。targetUserId 告诉后续启动逻辑把原 Intent 交给哪个 user;userHandle 仍表示结果从哪个 source user 被解析出来。

9. 最终合并规则 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java

java
if (shouldSkipCurrentProfile(
        computer, intent, resolvedType, userId)) {
    candidates = resolveInfoFromCrossProfileDomainInfo(
            crossProfileCandidates);
    return new QueryIntentActivitiesResult(
            computer.applyPostResolutionFilter(
                    candidates, instantAppPkgName,
                    allowDynamicSplits, filterCallingUid,
                    resolveForStart, userId, intent));
}

if (pkgName == null && intent.hasWebURI()) {
    if (!addInstant &&
            ((candidates.size() <= 1
                    && crossProfileCandidates.isEmpty())
            || (candidates.isEmpty()
                    && !crossProfileCandidates.isEmpty()))) {
        candidates.addAll(resolveInfoFromCrossProfileDomainInfo(
                crossProfileCandidates));
    } else {
        candidates = filterCandidatesWithDomainPreferredActivitiesLPr(
                computer, intent, matchFlags, candidates,
                crossProfileCandidates, userId,
                areWebInstantAppsDisabled, resolveForStart,
                pkgSettingFunction);
    }
} else {
    candidates.addAll(resolveInfoFromCrossProfileDomainInfo(
            crossProfileCandidates));
}

skip 当前 profile 时不再混合本地候选;普通 Web Intent 只有在候选数量和 Instant App 条件满足时才直接加入跨 profile 结果,否则通过 domain preference 重新筛选。非 Web Intent 直接把 forwarding ResolveInfo 追加到候选列表。

10. 清理与持久化 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
public void removeCrossProfileIntentFilter(
        IntentFilter intentFilter, int sourceUserId,
        int targetUserId) {
    removeCrossProfileIntentFilter_enforcePermission();
    final int callingUid = Binder.getCallingUid();
    mUserManager.enforceCrossProfileIntentFilterAccess(
            sourceUserId, targetUserId, callingUid, false);
    synchronized (mLock) {
        CrossProfileIntentResolver resolver =
                mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
        resolver.removeFilter(intentFilter, targetUserId);
    }
    scheduleWritePackageRestrictions(sourceUserId);
}

过滤器属于 source user 的设置文件;删除同样需要访问控制并持久化 source user。包卸载和 user 删除时还会清理对应的 cross-profile resolver,避免 forwarding Activity 指向失效目标。

11. 排查清单 ​

  1. 当前 profile 没有结果:检查是否命中 SKIP_CURRENT_PROFILE。
  2. 跨 profile 没有转发:检查 filter action、目标 user、UserManager profile 关系和 ONLY_IF_NO_MATCH_FOUND。
  3. 只有 Web 链接异常:检查 domain approval、parent profile restriction 和 handleAllWebDataURI。
  4. 链式转发过多或循环:检查 FLAG_ALLOW_CHAINED_RESOLUTION 与 visited user 集合。
  5. forwarding ResolveInfo 存在但启动失败:检查目标 user 是否启用、forwarding Activity 的 targetUserId 和跨用户权限。
  6. 配置修改不生效:检查 source user package restrictions 是否写回,以及 resolver 是否使用旧 snapshot。

12. 源码阅读路线 ​

  1. CrossProfileIntentFilter:字段、flags、访问控制与 XML 状态。
  2. PackageManagerService.addCrossProfileIntentFilter/removeCrossProfileIntentFilter:权限、去重和持久化。
  3. CrossProfileIntentResolverEngine.resolveIntentInternal:按目标 user 分组、DFS 和 chained resolution。
  4. shouldSkipCurrentProfile:源 profile 跳过条件。
  5. DefaultCrossProfileResolver:skip、only-if-no-match 和目标查询策略。
  6. ComputerEngine.createForwardingResolveInfoUnchecked 与结果合并:理解 forwarding Activity 如何进入最终列表。

跨 Profile 解析的核心是“源 user 的 filter 决定是否转发,目标 user 的 resolver 决定转发到哪里,Engine 决定如何与本地和 Web 候选合并”。把 forwarding Activity 当作普通业务 Activity,或把 ONLY_IF_NO_MATCH_FOUND 当作空结果判断,都会得出错误结论。