Skip to content

Package Visibility 过滤

追踪包可见性规则、UID 缓存、查询出口和安装状态边界,帮助定位“查询不到包”的真实原因。

基于android-17.0.0_r1
AndroidPMSAppsFilterPackageVisibility

Package Visibility 过滤 ​

本文面向已经读过 ComputerEngine 查询引擎 和 多用户下的 PMS 管理 的读者。它解决一个常见但容易误判的问题:应用调用 PackageManager 找不到另一个包时,究竟是包不存在、用户未安装,还是 AppsFilter 按调用 UID 过滤了结果。本文不重复解析 <queries> manifest 语法,而是沿 Android 17 的规则计算、缓存和查询出口追踪一次完整判断。

1. 过滤器边界 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/AppsFilterImpl.java
  • frameworks/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 对集合。

java
// 典型关系:新包与现有包之间建立双向可见性
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 防止重复工作;若构建过程中再次失效,则延迟重试。

java
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 不可见而从结果中删除。

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

java
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 复用后沿用旧可见性。

java
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 记录重建原因、包数量、用户数量和耗时。调试“查询不到包”时,建议按以下顺序取证:

  1. 使用故障应用的真实 UID/user 调用同一个 PackageManager API;
  2. 确认目标包对该 user 的安装/隐藏状态;
  3. 查看 shouldFilterApplication 的调用出口和 canQueryPackage 特殊分支;
  4. 检查 AppsFilter cache 是否刚因安装、用户创建或包删除而重建;
  5. 最后才检查 <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. 源码练习 ​

  1. 选择 getPackageInfo 和 queryIntentActivities 两个 API,分别找出 ComputerEngine 中的 shouldFilterApplication 调用点,并说明过滤发生在候选生成前还是后。
  2. 构造“目标包已安装到 user 10,但调用 UID 没有查询关系”的输入,区分 PackageState 存在、user 安装和 visibility 三个独立条件。
  3. 追踪包删除时 removeAppIdFromVisibilityCache 的索引移动,解释为什么 UID 复用要求清理 appId 而不是只清理包名。
  4. 给定安装器在包提交完成前需要查询目标包,定位 canQueryPackage 的 update-owner/initiating-package 分支,并说明它为什么不能替代普通应用的 <queries> 声明。