跨 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 |
CrossProfileIntentResolverEngine | DFS 遍历目标 profile 并合并结果 |
CrossProfileResolver | 某一对 user 的具体解析策略 |
IntentForwarderActivity | 作为候选结果的系统转发组件 |
2. Filter 数据
源码文件:frameworks/base/services/core/java/com/android/server/pm/CrossProfileIntentFilter.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
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 路由因此不是普通应用可以随意写入的全局表。
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
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 图中出现环。
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. 分组与链式解析
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 不再重复解析。
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
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
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
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
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
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. 排查清单
- 当前 profile 没有结果:检查是否命中
SKIP_CURRENT_PROFILE。 - 跨 profile 没有转发:检查 filter action、目标 user、UserManager profile 关系和
ONLY_IF_NO_MATCH_FOUND。 - 只有 Web 链接异常:检查 domain approval、parent profile restriction 和
handleAllWebDataURI。 - 链式转发过多或循环:检查
FLAG_ALLOW_CHAINED_RESOLUTION与 visited user 集合。 - forwarding ResolveInfo 存在但启动失败:检查目标 user 是否启用、forwarding Activity 的
targetUserId和跨用户权限。 - 配置修改不生效:检查 source user package restrictions 是否写回,以及 resolver 是否使用旧 snapshot。
12. 源码阅读路线
CrossProfileIntentFilter:字段、flags、访问控制与 XML 状态。PackageManagerService.addCrossProfileIntentFilter/removeCrossProfileIntentFilter:权限、去重和持久化。CrossProfileIntentResolverEngine.resolveIntentInternal:按目标 user 分组、DFS 和 chained resolution。shouldSkipCurrentProfile:源 profile 跳过条件。DefaultCrossProfileResolver:skip、only-if-no-match 和目标查询策略。ComputerEngine.createForwardingResolveInfoUnchecked与结果合并:理解 forwarding Activity 如何进入最终列表。
跨 Profile 解析的核心是“源 user 的 filter 决定是否转发,目标 user 的 resolver 决定转发到哪里,Engine 决定如何与本地和 Web 候选合并”。把 forwarding Activity 当作普通业务 Activity,或把 ONLY_IF_NO_MATCH_FOUND 当作空结果判断,都会得出错误结论。
