Package Visibility 过滤
本文面向已经读过 ComputerEngine 查询引擎 和 多用户下的 PMS 管理 的读者。它解决一个常见但容易误判的问题:应用调用 PackageManager 找不到另一个包时,究竟是包不存在、用户未安装,还是 AppsFilter 按调用 UID 过滤了结果。本文不重复解析 <queries> manifest 语法,而是沿 Android 17 的规则计算、缓存和查询出口追踪一次完整判断。
1. 过滤器边界
源码文件:
frameworks/base/services/core/java/com/android/server/pm/AppsFilterImpl.javaframeworks/base/services/core/java/com/android/server/pm/ComputerEngine.java
AppsFilterImpl 保存“谁能看见谁”的规则和缓存;ComputerEngine 负责把过滤结果应用到 getPackageInfo、getInstalledPackages、Intent 查询、Provider 查询等公开出口。PMS 仍拥有包状态,但不直接把完整 PackageState 暴露给调用方。
过滤结果是有方向的:A 能看见 B,不代表 B 能看见 A。缓存键包含 subject/recipient UID 关系,并进一步结合 userId 和包状态判断;不能把“包在设备上存在”直接推导为“当前调用者可见”。
2. 规则来源
源码文件:frameworks/base/services/core/java/com/android/server/pm/AppsFilterImpl.java。
实现维护多类隐式可见关系,包括 force-queryable 包、共享 UID、系统签名/系统组件关系、instrumentation、uses-library、显式 queries package、queries intent/provider,以及系统配置中的 protected broadcast。新增或移除包时,过滤器会把这些关系加入或移出 UID 对集合。
// 典型关系:新包与现有包之间建立双向可见性
if (newPkgSetting.getPkg() != null && existingSetting.getPkg() != null
&& (pkgInstruments(newPkgSetting.getPkg(), existingSetting.getPkg())
|| pkgInstruments(existingSetting.getPkg(), newPkgSetting.getPkg()))) {
synchronized (mQueriesViaPackageLock) {
mQueriesViaPackage.add(newPkgSetting.getAppId(), existingSetting.getAppId());
mQueriesViaPackage.add(existingSetting.getAppId(), newPkgSetting.getAppId());
}
}这里的“关系集合”不是最终结果。它们只是 shouldFilterApplicationInternal 的输入;最终判断还会检查调用者是否自身、目标包是否 force-queryable、目标是否已安装到该 user,以及调用者/目标的 UID 和 user 组合。
3. UID 缓存
源码符号:updateEntireShouldFilterCacheInner、updateShouldFilterCacheForPackage。
规则变化后,过滤器可以异步重建整个 UID 对缓存。重建使用 Computer snapshot,遍历 package states 和 users,并把每个方向的 shouldFilter 结果写入 mShouldFilterCache。缓存重建期间会通过 mCacheValid 防止重复工作;若构建过程中再次失效,则延迟重试。
if (!mCacheValid.compareAndSet(CACHE_INVALID, CACHE_VALID)) {
return;
}
updateEntireShouldFilterCacheInner(snapshot, settings, users, USER_ALL);
if (!mCacheValid.compareAndSet(CACHE_VALID, CACHE_VALID)) {
updateEntireShouldFilterCacheAsync(pmInternal,
Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS), reason);
return;
}这解释了安装或用户创建后短时间内查询结果变化的原因:包状态提交和可见性缓存重建不是同一个临界区。调用方应以当前查询的 snapshot 和 cache 状态为准,不能把一次旧查询结果当作永久授权。
4. 查询出口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java。
shouldFilterApplication 在查询层被反复调用。以 Intent 查询为例,候选 ResolveInfo 生成后,ComputerEngine 会对候选包执行过滤;包即使有匹配的 intent,也可能因调用 UID 不可见而从结果中删除。
if (ps != null && shouldFilterApplication(
ps, filterCallingUid, userId)) {
continue;
}同一规则还出现在 getPackageInfo、getApplicationInfo、已安装/未安装包列表、Provider 解析、Instrumentation 查询和权限相关查询中。调试时必须先记录“哪个 API”,再定位对应出口;只在 queryIntentActivities 里修复不会改变 getPackageInfo 的可见性。
5. canQueryPackage
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java;符号:canQueryPackage。
canQueryPackage 是系统内部给其他服务使用的布尔判断,但它不是简单包装 shouldFilterApplication。源码对安装中/已安装包分别处理:若存在调用方包状态,优先使用现有可见性规则;若调用方包尚未完成安装,则允许某些 installer/update-owner 场景继续查询;找不到调用方包时返回 false。
public boolean canQueryPackage(int callingUid,
@Nullable String targetPackageName) {
PackageStateInternal pkg = getPackageStateForUid(callingUid);
if (pkg != null) {
return !shouldFilterApplication(
getPackageStateInternal(targetPackageName), callingUid,
UserHandle.getUserId(callingUid));
}
return false;
}实际源码还包含 update-owner 和 initiating-package 的特殊分支。它们的共同目的,是让安装事务在包尚未完全可见时仍能验证来源,同时不把这类临时授权扩大成普通应用的长期可见性。
6. 多用户影响
过滤缓存按 user 参与计算。新用户创建时,AppsFilterImpl.onUserCreated 重新计算相关 UID 对;用户删除时,onUserDeleted 移除该 user 的缓存。包是否对 user 安装、hidden、instant app 或 archived 状态,都会在 ComputerEngine 的具体查询出口继续参与判断。
因此跨用户故障要同时检查两个条件:目标包是否为该 user 安装,以及调用 UID 对目标 UID 是否有可见关系。系统 UID 的 dump 结果不能证明普通应用在工作资料或克隆用户下也能看到该包。
7. 包变更失效
包安装、更新、删除、组件变化和隐式访问授权都会使可见性缓存失效。AppsFilterImpl 通过 onChanged 通知快照/消费者;删除包时还会清理 appId 对应的缓存键,避免 UID 复用后沿用旧可见性。
private void removeAppIdFromVisibilityCache(int appId) {
synchronized (mCacheLock) {
for (int i = 0; i < mShouldFilterCache.size(); i++) {
if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
mShouldFilterCache.removeAt(i);
i--;
}
}
}
}这个 i-- 是删除遍历中的必要细节:移除当前 key 后,后续 key 左移,循环索引必须回退,否则会跳过相邻条目。它也说明缓存清理是按 appId 处理,而不是只删一个 package name。
8. 调试证据
ComputerEngine.dump 和 AppsFilterImpl.dumpQueries 可以输出可见性关系;源码中的 cache rebuild metrics 记录重建原因、包数量、用户数量和耗时。调试“查询不到包”时,建议按以下顺序取证:
- 使用故障应用的真实 UID/user 调用同一个 PackageManager API;
- 确认目标包对该 user 的安装/隐藏状态;
- 查看
shouldFilterApplication的调用出口和canQueryPackage特殊分支; - 检查 AppsFilter cache 是否刚因安装、用户创建或包删除而重建;
- 最后才检查
<queries>、权限或 intent 是否缺失。
如果 getPackageInfo 返回 null 而 system UID 能查到,优先怀疑 visibility;如果 Intent 查询为空但显式包查询可见,则继续检查 component/intent 匹配,而不是继续修改 AppsFilter。
9. 设计边界
包可见性不是访问权限。可见只表示调用者可以在 PackageManager 结果中观察到目标包,不代表能读取其数据、启动其组件或访问其 Provider;后续 API 仍会执行权限、exported、组件启用状态和 user 校验。
同样,forceQueryable、系统包和 shared UID 是规则输入,不是“所有 API 永远绕过过滤”的总开关。每个查询出口仍可能额外过滤 instant app、未安装包、archived 包或不可见组件。
10. 源码练习
- 选择
getPackageInfo和queryIntentActivities两个 API,分别找出ComputerEngine中的shouldFilterApplication调用点,并说明过滤发生在候选生成前还是后。 - 构造“目标包已安装到 user 10,但调用 UID 没有查询关系”的输入,区分
PackageState存在、user 安装和 visibility 三个独立条件。 - 追踪包删除时
removeAppIdFromVisibilityCache的索引移动,解释为什么 UID 复用要求清理 appId 而不是只清理包名。 - 给定安装器在包提交完成前需要查询目标包,定位
canQueryPackage的 update-owner/initiating-package 分支,并说明它为什么不能替代普通应用的<queries>声明。
