Skip to content

ComputerEngine

拆解 Computer 查询引擎如何组织包状态、组件候选、用户策略和 framework 对象生成。

基于android-17.0.0_r1
AndroidPackageManagerServiceComputerEngineIntent解析包可见性源码阅读

ComputerEngine ​

ComputerEngine 是 Android 17 Package Manager 的只读查询实现。它并不负责安装、卸载或把状态写回 Settings,而是把 PMS 当前可查询的数据组织成一个 Computer,再由这个对象执行包、组件、UID、共享库和 Intent 查询。

本篇的学习目标是读懂“一个查询结果怎样产生”,而不是记住 ComputerEngine 有多少方法。文章会沿源码追踪四件事:

  • Computer 为什么规定查询函数必须是纯函数,ComputerEngine 为什么同时保存快照字段和少量服务引用;
  • queryIntentActivitiesInternal() 怎样处理用户、flags、显式组件、selector、instant app、跨 profile 和后过滤;
  • getInstalledApplications() 怎样在已安装、未安装、归档、APEX、共享库和可见性之间做分支;
  • ComputerLocked 为什么只覆盖极少数方法,以及这些方法如何避免读取未稳定发布的数据。

PackageManagerInternal 已经解释过“system_server 如何拿到 Computer”,ComputerEngine 则回答“拿到之后具体查什么、按什么顺序查”。下一篇会专门讨论 snapshot 失效、重建、版本和锁的并发机制;本篇只在需要理解查询语义的地方引用这些入口。

1. 查询契约 ​

1.1 Computer 的约束 ​

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

java
/**
 * A {@link Computer} provides a set of functions that can operate on live data or snapshot
 * data.  At this time, the {@link Computer} is implemented by the
 * {@link ComputerEngine}, which is in turn extended by {@link ComputerLocked}.
 *
 * New functions must be added carefully.
 * <ol>
 * <li> New functions must be true functions with respect to data collected in a
 * {@link PackageManagerService.Snapshot}.  Such data may never be modified from inside a {@link Computer}
 * function.
 * </li>
 *
 * <li> A new function must be implemented in {@link ComputerEngine}.
 * </li>
 *
 * <li> A new function must be overridden in {@link ComputerLocked} if the function
 * cannot safely access live data without holding the PackageManagerService lock.  The
 * form of the {@link ComputerLocked} function must be a single call to the
 * {@link ComputerEngine} implementation, wrapped in a <code>synchronized</code>
 * block.  Functions in {@link ComputerLocked} should never include any other code.
 * </li>
 * </ol>
 */
public interface Computer extends PackageDataSnapshot {

这段注释就是查询引擎的设计合同:

  1. Computer 函数针对 Snapshot 中的数据必须是“true function”,不能在查询过程中修改这些数据。
  2. 新函数先在 ComputerEngine 实现;只有 live data 不能安全读取时,才在 ComputerLocked 覆盖。
  3. ComputerLocked 的覆盖只能是“加锁后调用 ComputerEngine 实现”,不能把业务逻辑复制一份。

因此看到一个查询方法时,先问它读取的是 snapshot 字段、未快照化的服务引用,还是 PMS live state。这个判断比方法名更重要。

1.2 查询接口的分组 ​

Computer 接口把查询按结果和策略分成几组:

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

java
int getVersion();
Computer use();
default int getUsed() { return 0; }

@NonNull List<ResolveInfo> queryIntentActivitiesInternal(
        Intent intent, String resolvedType,
        @PackageManager.ResolveInfoFlagsBits long flags,
        @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
        int filterCallingUid, int callingPid, int userId,
        boolean resolveForStart, boolean allowDynamicSplits);

ActivityInfo getActivityInfo(ComponentName component, long flags, int userId);
ActivityInfo getActivityInfoInternal(ComponentName component, long flags,
        int filterCallingUid, int userId);

AndroidPackage getPackage(String packageName);
AndroidPackage getPackage(int uid);
ApplicationInfo getApplicationInfo(String packageName, long flags, int userId);
ApplicationInfo getApplicationInfoInternal(String packageName, long flags,
        int filterCallingUid, int userId);

PackageInfo getPackageInfo(String packageName, long flags, int userId);
PackageInfo getPackageInfoInternal(String packageName, long versionCode, long flags,
        int filterCallingUid, int userId);

方法大致分为三层:

  • 数据读取:getPackage()、getPackageStateInternal()、getPackagesForUid();返回 AndroidPackage 或 PackageStateInternal 等内部对象。
  • 对象生成:getApplicationInfoInternal()、getPackageInfoInternal()、getActivityInfoInternal();把包模型和 per-user 状态转换成 framework 返回对象。
  • 解析与策略:queryIntentActivitiesInternal()、shouldFilterApplication()、updateFlagsForResolve();先确定候选集合,再应用用户、权限、instant app 和可见性规则。

这三层不是互相独立的。Intent 查询会调用组件解析器,再调用 getActivityInfo() 或后过滤;应用列表会从 PackageStateInternal 生成 ApplicationInfo,并在生成前后调用过滤策略。

2. 引擎数据 ​

2.1 Snapshot参数 ​

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

java
private final int mVersion;

// The administrative use counter.
private int mUsed = 0;

// Cached attributes.  The names in this class are the same as the
// names in PackageManagerService; see that class for documentation.
protected final Settings mSettings;
private final WatchedSparseIntArray mIsolatedOwners;
private final WatchedArrayMap<String, AndroidPackage> mPackages;
private final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation;
private final SharedLibrariesRead mSharedLibraries;
private final ComponentName mLocalResolveComponentName;
private final ActivityInfo mResolveActivity;
private final WatchedSparseBooleanArray mWebInstantAppsDisabled;
private final ActivityInfo mLocalInstantAppInstallerActivity;
private final ResolveInfo mInstantAppInstallerInfo;
private final InstantAppRegistry mInstantAppRegistry;
private final ApplicationInfo mLocalAndroidApplication;
private final AppsFilterSnapshot mAppsFilter;
private final WatchedArrayMap<String, Integer> mFrozenPackages;

这些字段可以按“查询需要什么”理解:

字段查询责任数据是否属于快照
mSettings包设置、共享 UID、per-user 状态是,使用 new Settings(args.settings) 包装
mPackages已解析的 AndroidPackage是
mComponentResolverActivity、Service、Provider、Receiver 匹配是接口引用,内部快照由 resolver 提供
mAppsFilterpackage visibility是
mSharedLibraries共享库查找与依赖是
mIsolatedOwnersisolated UID 到 owner UID 的映射是
mFrozenPackages安装/更新期间的冻结包是
mPermissionManager权限检查与 app-op 包查询引用外部服务,不复制
mUserManageruser 存在、跨 user/profile 权限引用外部服务
mContext、mInjector资源、兼容性和依赖入口引用外部服务

表中最后一组“引用外部服务”很关键:ComputerEngine 不是把 PMS 全部字段深拷贝一遍。它只复制需要稳定读取的可观察数据;对于从未由 PMS lock 保护、或必须使用实时服务的对象,保留引用并依靠这些服务自己的并发合同。

2.2 复制边界 ​

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

java
ComputerEngine(PackageManagerService.Snapshot args, int version) {
    mVersion = version;
    mSettings = new Settings(args.settings);
    mIsolatedOwners = args.isolatedOwners;
    mPackages = args.packages;
    mSharedLibraries = args.sharedLibraries;
    mInstrumentation = args.instrumentation;
    mWebInstantAppsDisabled = args.webInstantAppsDisabled;
    mLocalResolveComponentName = args.resolveComponentName;
    mResolveActivity = args.resolveActivity;
    mLocalInstantAppInstallerActivity = args.instantAppInstallerActivity;
    mInstantAppInstallerInfo = args.instantAppInstallerInfo;
    mInstantAppRegistry = args.instantAppRegistry;
    mLocalAndroidApplication = args.androidApplication;
    mAppsFilter = args.appsFilter;
    mFrozenPackages = args.frozenPackages;
    mComponentResolver = args.componentResolver;

    // The following are not cached copies.  Instead they are
    // references to outside services.
    mPermissionManager = args.service.mPermissionManager;
    mUserManager = args.service.mUserManager;
    mContext = args.service.mContext;
    mInjector = args.service.mInjector;
    mApexManager = args.service.mApexManager;
    mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
    mDefaultAppProvider = args.service.getDefaultAppProvider();
    mDomainVerificationManager = args.service.mDomainVerificationManager;
    mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
    mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
            mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);

    // Used to reference PMS attributes that are primitives and which are not
    // updated under control of the PMS lock.
    mService = args.service;
}

args.settings、args.packages、args.appsFilter 等来自 PMS 的 Snapshot,它们构成查询视图;权限服务、UserManager 和 Context 则直接来自 PMS service。最后的 mService 只用于少量 primitive 或未受 PMS lock 管理的属性。

阅读某个新字段时,应该先判断它属于哪一类:如果字段是 args 中的快照成员,查询应保持只读;如果字段来自 args.service,要继续查该服务自己的线程安全和锁顺序。

2.3 版本和使用计数 ​

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

java
@Override
public int getVersion() {
    return mVersion;
}

/** Record that the snapshot was used. */
public final Computer use() {
    mUsed++;
    return this;
}

/** Return the usage counter. */
public final int getUsed() {
    return mUsed;
}

use() 返回自身,所以 PMS 可以在返回 snapshot 的同时累加使用次数。mUsed 主要服务于 snapshot 统计,并不参与查询结果,也不是引用计数释放机制。不要据此推断 snapshot 会在 getUsed() 归零后销毁;生命周期由 PMS 的 snapshot 缓存管理。

3. 包与对象 ​

3.1 原始包对象 ​

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

java
@Override
public AndroidPackage getPackage(String packageName) {
    packageName = resolveInternalPackageName(
            packageName, PackageManager.VERSION_CODE_HIGHEST);
    return mPackages.get(packageName);
}

@Override
public AndroidPackage getPackage(int uid) {
    final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
    AndroidPackage pkg = null;
    final int numPackages = packageNames == null ? 0 : packageNames.length;
    for (int i = 0; pkg == null && i < numPackages; i++) {
        pkg = mPackages.get(packageNames[i]);
    }
    return pkg;
}

getPackage(String) 先通过 resolveInternalPackageName() 处理重命名包和静态库,再做 map lookup;它本身不按 user 过滤。getPackage(int uid) 复用 getPackagesForUidInternal(uid, Process.SYSTEM_UID),再从结果中找到第一个有 AndroidPackage 的对象。shared UID 对应多个包,所以这个返回值不能被当成“UID 对应唯一包”;需要完整成员集合时,应使用 shared-user 专门的查询方法。

3.2 状态与包 ​

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

java
@Override
public final PackageStateInternal getPackageStateInternal(String packageName) {
    return getPackageStateInternal(packageName, Binder.getCallingUid());
}

public PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
    packageName = resolveInternalPackageNameInternalLocked(
            packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
    return mSettings.getPackage(packageName);
}

@Override
public PackageStateInternal getPackageStateFiltered(@NonNull String packageName,
        int callingUid, @UserIdInt int userId) {
    packageName = resolveInternalPackageNameInternalLocked(
            packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
    var packageState = mSettings.getPackage(packageName);
    if (shouldFilterApplication(packageState, callingUid, userId)) {
        return null;
    }
    return packageState;
}

getPackageStateInternal() 的两个重载都会先规范化重命名包,然后直接返回 Settings 状态;传入 callingUid 并不自动执行 visibility 过滤。真正按 user 和 calling UID 过滤的是单独的 getPackageStateFiltered()。这是一个重要边界:需要隐藏 package existence 的调用方必须选择 filtered 变体,不能仅仅调用带 UID 参数的 internal 变体。

PackageStateInternal 可能存在而 getPkg() 为 null。这表示 PMS 仍保留包状态,但当前没有可用的解析包对象,例如未安装保留数据或解析内容尚未恢复。后面的 generateApplicationInfoFromSettings() 正是为这种状态保留的分支。

3.3 ApplicationInfo ​

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

java
@Override
public final ApplicationInfo getApplicationInfo(String packageName, long flags, int userId) {
    final int callingUid = Binder.getCallingUid();
    return getApplicationInfoInternal(packageName, flags, callingUid, userId);
}

@Override
public final ApplicationInfo getApplicationInfoInternal(String packageName, long flags,
        int filterCallingUid, int userId) {
    if (!mUserManager.exists(userId)) return null;
    flags = updateFlagsForApplication(flags, userId);
    if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) {
        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                false /* requireFullPermission */, false /* checkShell */,
                "get application info");
    }
    return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId);
}

调用顺序是:检查 user→调整 application flags→执行 cross-user 权限检查→进入 getApplicationInfoInternalBody()。body 会规范化重命名包,处理 APEX、共享库和 visibility,再根据 AndroidPackage 是否存在选择 PackageInfoUtils 或 Settings 生成路径。把“入口权限检查”和“对象生成”分成两层,是 ComputerEngine 中常见的结构。

4. Intent 查询 ​

4.1 入口guard ​

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

java
public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(
        Intent intent, String resolvedType, long flags,
        int filterCallingUid, int userId) {
    return queryIntentActivitiesInternal(
            intent, resolvedType, flags, 0 /*privateResolveFlags*/, filterCallingUid,
            Process.INVALID_PID, userId,
            /*resolveForStart*/ false, /*allowDynamicSplits*/ true);
}

这个重载没有自行解析 Intent,而是补齐默认参数进入完整实现。默认值代表普通“查询 activities”场景:不带 private resolve flags、没有有效 calling PID、不为启动动作专门解析、允许 dynamic splits。

完整实现的第一步不是匹配组件,而是验证 user 和调整 flags:

java
public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(
        Intent intent, String resolvedType, long flags,
        long privateResolveFlags, int filterCallingUid, int callingPid, int userId,
        boolean resolveForStart, boolean allowDynamicSplits) {
    if (!mUserManager.exists(userId)) return Collections.emptyList();

    // Allow to match activities of quarantined packages.
    flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;

    final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
    enforceCrossUserPermission(Binder.getCallingUid(), userId,
            false /* requireFullPermission */, false /* checkShell */,
            "query intent activities");

这里有三个容易漏掉的状态:

  • user 不存在直接返回空列表,不会进入 resolver;
  • 查询内部会补上 MATCH_QUARANTINED_COMPONENTS,所以后续匹配允许处理被 quarantine 的组件;
  • filterCallingUid 用于结果过滤,而跨 user 权限检查使用真实 Binder.getCallingUid()。本地调用方若清掉 Binder identity,仍必须把原始过滤 UID 作为参数保留下来。

4.2 selector与组件 ​

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

java
final String pkgName = intent.getPackage();
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
    if (intent.getSelector() != null) {
        originalIntent = intent;
        intent = intent.getSelector();
        comp = intent.getComponent();
    }
}

flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
        comp != null || pkgName != null /*onlyExposedExplicitly*/,
        isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId, resolvedType,
                flags));

var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
        false /* isReceiver */, resolveForStart, filterCallingUid, callingPid);
args.platformCompat = mInjector.getCompatibility();
args.snapshot = this;

Intent 可能有 selector。源码先保存原始 Intent,再把 selector 作为真正的匹配输入;匹配完成后还会恢复 args.intent = originalIntent,重新验证所有组件是否满足原始 Intent。这样可以避免 selector 只满足内部查询而原始约束被绕过。

updateFlagsForResolve() 还根据调用者、是否启动、显式组件和 camera intent 调整 flags。它不是一个简单的 bitwise OR 工具,而是把 instant app 能见范围和特殊 intent 策略集中到解析前。

4.3 显式组件分支 ​

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

java
List<ResolveInfo> list = Collections.emptyList();
boolean skipPostResolution = false;
if (comp != null) {
    final ActivityInfo ai = getActivityInfo(comp, flags, userId);
    if (ai != null) {
        final boolean matchInstantApp =
                (flags & PackageManager.MATCH_INSTANT) != 0;
        final boolean matchVisibleToInstantAppOnly =
                (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
        final boolean matchExplicitlyVisibleOnly =
                (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
        final boolean isCallerInstantApp = instantAppPkgName != null;
        final boolean isTargetSameInstantApp =
                comp.getPackageName().equals(instantAppPkgName);
        final boolean isTargetInstantApp =
                (ai.applicationInfo.privateFlags
                        & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
        final boolean isTargetVisibleToInstantApp =
                (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
        final boolean blockInstantResolution =
                !isTargetSameInstantApp
                        && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
                        || (matchVisibleToInstantAppOnly && isCallerInstantApp
                        && !isTargetVisibleToInstantApp));
        final boolean resolveForStartNonExported = resolveForStart
                && !ai.exported
                && !isCallerSameApp(pkgName, filterCallingUid);
        final boolean blockNormalResolution =
                (!resolveForStart || resolveForStartNonExported)
                && !isTargetInstantApp
                && !isCallerInstantApp
                && shouldFilterApplication(
                        getPackageStateInternal(ai.applicationInfo.packageName,
                                Process.SYSTEM_UID), filterCallingUid, userId);
        if (!blockInstantResolution && !blockNormalResolution) {
            final ResolveInfo ri = new ResolveInfo();
            ri.activityInfo = ai;
            ri.userHandle = UserHandle.of(userId);
            list = new ArrayList<>(1);
            list.add(ri);
            SaferIntentUtils.enforceIntentFilterMatching(args, list);
        }
    }
}

显式组件并不意味着“只要类存在就返回”。源码仍检查:

  • caller 和 target 是否是 instant app,以及目标是否声明对 instant app 可见;
  • 启动场景下非 exported activity 是否属于 caller 自己;
  • 普通调用者是否能看到目标 package;
  • 最终 ResolveInfo 是否满足原始 Intent filter。

只有通过这些条件才构造单元素列表。若 getActivityInfo() 返回 null 或任一 block 条件为真,结果保持空列表。

4.4 隐式候选 ​

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

java
QueryIntentActivitiesResult lockedResult =
        queryIntentActivitiesInternalBody(
                intent, resolvedType, flags, filterCallingUid, userId,
                resolveForStart, allowDynamicSplits, pkgName, instantAppPkgName);
if (lockedResult.answer != null) {
    skipPostResolution = true;
    list = lockedResult.answer;
} else {
    if (lockedResult.addInstant) {
        String callingPkgName = getInstantAppPackageName(filterCallingUid);
        boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
        lockedResult.result = maybeAddInstantAppInstaller(
                lockedResult.result, intent, resolvedType, flags,
                userId, resolveForStart, isRequesterInstantApp);
    }
    if (lockedResult.sortResult) {
        lockedResult.result.sort(RESOLVE_PRIORITY_SORTER);
    }
    list = lockedResult.result;
}
SaferIntentUtils.blockNullAction(args, list);

queryIntentActivitiesInternalBody() 返回一个带控制信息的结果,而不是简单的 List。answer 非空时表示 body 已经给出最终答案,外层跳过 post-resolution filter;否则外层根据 addInstant 添加 instant app installer,根据 sortResult 排序,再阻止没有 action 的非法结果。

这种返回结构把“候选列表”和“后续阶段是否仍需运行”一起传递,避免外层重复推断。读源码时不要只追 result 字段,也要追 answer、addInstant 和 sortResult。

4.5 profile与package ​

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

java
public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(
        Intent intent, String resolvedType, long flags, int filterCallingUid, int userId,
        boolean resolveForStart, boolean allowDynamicSplits,
        String pkgName, String instantAppPkgName) {
    boolean sortResult = false;
    boolean addInstant = false;
    List<ResolveInfo> result = new ArrayList<>();
    List<CrossProfileDomainInfo> crossProfileResults = new ArrayList<>();
    if (pkgName == null) {
        if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
                resolvedType, userId)) {
            final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
                    intent, resolvedType, flags, userId);
            if (queryResult != null) {
                result.addAll(filterIfNotSystemUser(queryResult, userId));
            }
        }
        addInstant = isInstantAppResolutionAllowed(intent, result,
                userId, false /*skipPackageCheck*/, flags);
        boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
        crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(this, intent,
                resolvedType, userId, flags, pkgName, hasNonNegativePriorityResult,
                resolveForStart, mSettings::getPackage);
        if (intent.hasWebURI() || !crossProfileResults.isEmpty()) sortResult = true;
    } else {
        final PackageStateInternal setting =
                getPackageStateInternal(pkgName, Process.SYSTEM_UID);
        if (setting != null && setting.getAndroidPackage() != null
                && (resolveForStart || !shouldFilterApplication(setting,
                filterCallingUid, userId))) {
            final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
                    intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
                    userId);
            if (queryResult != null) {
                result.addAll(filterIfNotSystemUser(queryResult, userId));
            }
        }
        if (result.size() == 0) {
            addInstant = isInstantAppResolutionAllowed(intent, null /*result*/, userId,
                    true /*skipPackageCheck*/, flags);
        }
        crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(this, intent,
                resolvedType, userId, flags, pkgName, false,
                resolveForStart, mSettings::getPackage);
    }

隐式查询有两条主要路径:

  1. 没有 package 限定:先检查是否跳过当前 profile,再让 ComponentResolver 查询 activities;之后判断是否允许 instant app,并解析跨 profile 结果。
  2. 指定 package:先从 Settings 找 package state,用 Process.SYSTEM_UID 做包状态 lookup,再用真实 filterCallingUid 决定是否过滤;如果当前包没有结果,才尝试 instant app fallback。

无论哪条路径,组件匹配和跨 profile 解析都发生在同一个 ComputerEngine 上,后续还要合并结果、处理 preferred activity 和排序。

4.6 后过滤 ​

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

java
if (originalIntent != null) {
    // We also have to ensure all components match the original intent
    args.intent = originalIntent;
    SaferIntentUtils.enforceIntentFilterMatching(args, list);
}

return skipPostResolution ? list : applyPostResolutionFilter(
        list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
        resolveForStart, userId, intent);

这里是 Intent 查询的最后一道边界:

  • selector 场景重新以原始 Intent 验证结果;
  • 普通结果进入 applyPostResolutionFilter(),继续处理 instant app 可见性、动态 split 和启动场景;
  • body 已经给出 answer 时跳过后过滤,因为 body 已经承担了最终结果的构造。

4.7 Service差异 ​

queryIntentServicesInternal() 复用相似结构,但 service 查询的参数和 post filter 不完全相同:

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

java
public final @NonNull List<ResolveInfo> queryIntentServicesInternal(
        Intent intent, String resolvedType, long flags, int userId,
        int callingUid, int callingPid, boolean includeInstantApps,
        boolean resolveForStart) {
    if (!mUserManager.exists(userId)) return Collections.emptyList();
    enforceCrossUserOrProfilePermission(Binder.getCallingUid(), userId,
            false /*requireFullPermission*/, false /*checkShell*/,
            "query intent services");
    final String instantAppPkgName = getInstantAppPackageName(callingUid);
    flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
            false /* isImplicitImageCaptureIntentAndNotSetByDpc */);

service 查询使用 enforceCrossUserOrProfilePermission(),而 activity 查询使用 enforceCrossUserPermission();这体现了 profile 路由上的不同权限要求。service 的 callingUid、callingPid 和 resolveForStart 也会传进 SaferIntentUtils.IntentArgs,不能因为结果类型都是 ResolveInfo 就把两条路径当成同一套策略。

5. 应用列表 ​

5.1 入口条件 ​

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

java
@NonNull
@Override
public List<ApplicationInfo> getInstalledApplications(
        @PackageManager.ApplicationInfoFlagsBits long flags,
        @UserIdInt int userId, int callingUid, boolean forceAllowCrossUser) {
    if (getInstantAppPackageName(callingUid) != null) {
        return Collections.emptyList();
    }
    if (!mUserManager.exists(userId)) return Collections.emptyList();
    flags = updateFlagsForApplication(flags, userId);
    final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
    final boolean listApex = (flags & MATCH_APEX) != 0;
    final boolean listArchivedOnly = !listUninstalled
            && (flags & MATCH_ARCHIVED_PACKAGES) != 0;

    if (!forceAllowCrossUser) {
        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                false /* requireFullPermission */, false /* checkShell */,
                "get installed application info");
    }

列表查询先处理三个“直接为空”的条件:instant app caller、不存在的 user、跨 user 权限不足。然后从 flags 得出三个分支变量:是否包含已知但未安装包、是否包含 APEX、是否仅列归档包。

forceAllowCrossUser 是内部调用方的特权开关,但它只跳过这一个 cross-user permission check,不会跳过后面的 package visibility 过滤。不能把它理解成“返回全部包”。

5.2 未安装与归档分支 ​

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

java
final ArrayMap<String, ? extends PackageStateInternal> packageStates =
        getPackageStates();
if (listUninstalled || listArchivedOnly) {
    list = new ArrayList<>(packageStates.size());
    for (PackageStateInternal ps : packageStates.values()) {
        ApplicationInfo ai;
        long effectiveFlags = flags;
        if (ps.isSystem()) {
            effectiveFlags |= PackageManager.MATCH_ANY_USER;
        }
        if (ps.getPkg() != null) {
            if (!listApex && ps.getPkg().isApex()) {
                continue;
            }
            PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
            if (listArchivedOnly && !userState.isInstalled()
                    && userState.getArchiveState() == null) {
                continue;
            }
            if (filterSharedLibPackage(ps, callingUid, userId)) {
                continue;
            }
            if (shouldFilterApplication(ps, callingUid, userId)) {
                continue;
            }
            ai = PackageInfoUtils.generateApplicationInfo(ps.getPkg(), effectiveFlags,
                    ps.getUserStateOrDefault(userId), userId, ps);
            if (ai != null) {
                ai.packageName = resolveExternalPackageName(ps.getPkg());
            }
        } else {
            // Shared lib filtering done in generateApplicationInfoFromSettingsLPw
            // and already converts to externally visible package name
            ai = generateApplicationInfoFromSettings(ps.getPackageName(),
                    effectiveFlags, callingUid, userId);
        }
        if (ai != null) {
            list.add(ai);
        }
    }
}

这个分支遍历的是全部 PackageStateInternal,不是只有 mPackages。因此它能表达“状态还在但 APK 不可用”的包。每个状态依次经过:

  1. system package 补 MATCH_ANY_USER;
  2. APEX 包开关;
  3. 归档状态判断;
  4. shared library 过滤;
  5. shouldFilterApplication() 可见性判断;
  6. 根据 pkg 是否为空选择对象生成器。

MATCH_KNOWN_PACKAGES 和 MATCH_ARCHIVED_PACKAGES 只扩大候选状态集合,不会取消 shouldFilterApplication()。这就是 flags 与 visibility 的关系:flags 决定“考虑哪些状态”,calling UID 决定“结果能否交给调用方”。

5.3 普通已安装分支 ​

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

java
} else {
    list = new ArrayList<>(mPackages.size());
    for (PackageStateInternal packageState : packageStates.values()) {
        final AndroidPackage pkg = packageState.getPkg();
        if (pkg == null) {
            continue;
        }
        if (!listApex && pkg.isApex()) {
            continue;
        }
        if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
            continue;
        }
        if (filterSharedLibPackage(packageState, callingUid, userId)) {
            continue;
        }
        if (shouldFilterApplication(packageState, callingUid, userId)) {
            continue;
        }
        ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(pkg, flags,
                packageState.getUserStateOrDefault(userId), userId, packageState);
        if (ai != null) {
            ai.packageName = resolveExternalPackageName(pkg);
            list.add(ai);
        }
    }
}
return list;

普通分支更严格:pkg == null、APEX 不在 flags、用户未安装、共享库过滤或 visibility 失败都会跳过。这里没有调用 generateApplicationInfoFromSettings(),因为普通列表只接受有解析包对象的已安装状态。

注意遍历来源仍是 packageStates.values(),而不是 mPackages.values()。这样可以在一个循环中统一处理 Settings 状态、per-user 安装位和解析包对象,避免两个 map 在更新期间产生不同的候选集合。

6. 可见性策略 ​

6.1 过滤重载 ​

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

java
boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid,
        @Nullable ComponentName component, @PackageManager.ComponentType int componentType,
        int userId, boolean filterUninstall);

boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid,
        int userId);

boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
        int callingUid, int userId);

boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps,
        int callingUid, int userId);

boolean shouldFilterApplicationIncludingUninstalledNotArchived(
        @Nullable PackageStateInternal ps, int callingUid, int userId);

方法重载体现了不同信息泄露风险:

  • 普通 shouldFilterApplication() 主要判断当前 user 是否可见、是否安装以及 component 是否可访问;
  • IncludingUninstalled 把“不存在”和“当前用户未安装”统一纳入过滤,避免通过返回差异泄露包存在性;
  • IncludingUninstalledNotArchived 额外把 archived 包排除;
  • shared UID 使用 SharedUserSetting 重载,因为一个 appId 下所有成员的可见性需要按 group 语义判断。

6.2 AppsFilter策略 ​

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

java
public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
        int callingUid, @Nullable ComponentName component,
        @PackageManager.ComponentType int componentType, int userId,
        boolean filterUninstall, boolean filterArchived) {
    if (Process.isSdkSandboxUid(callingUid)) {
        int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid);
        // SDK sandbox should be able to see its client app.
        if (ps != null && clientAppUid == UserHandle.getUid(userId, ps.getAppId())) {
            return false;
        }
    }
    // If we're in an isolated process, get the real calling UID.
    if (Process.isIsolated(callingUid)) {
        callingUid = getIsolatedOwner(callingUid);
    }
    final String instantAppPkgName = getInstantAppPackageName(callingUid);
    final boolean callerIsInstantApp = instantAppPkgName != null;
    final boolean packageArchivedForUser = ps != null
            && PackageArchiver.isArchived(ps.getUserStateOrDefault(userId));
    if (ps == null
            || (filterUninstall && !isSystemOrRootOrShell(callingUid)
            && !ps.isHiddenUntilInstalled()
            && !ps.getUserStateOrDefault(userId).isInstalled()
            && (!packageArchivedForUser || filterArchived))) {
        return callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid);
    }
    if (isCallerSameApp(ps.getPackageName(), callingUid)) {
        return false;
    }
    if (callerIsInstantApp) {
        if (ps.getUserStateOrDefault(userId).isInstantApp()) {
            return true;
        }
        if (component != null) {
            return !isComponentVisibleToInstantApp(component, componentType);
        }
        return !ps.getPkg().isVisibleToInstantApps();
    }
    if (ps.getUserStateOrDefault(userId).isInstantApp()) {
        if (canViewInstantApps(callingUid, userId)) {
            return false;
        }
        if (component != null) {
            return true;
        }
        return !mInstantAppRegistry.isInstantAccessGranted(
                userId, UserHandle.getAppId(callingUid), ps.getAppId());
    }
    final int appId = UserHandle.getAppId(callingUid);
    final SettingBase callingPs = mSettings.getSettingBase(appId);
    return mAppsFilter.shouldFilterApplication(this, callingUid, callingPs, ps, userId);
}

源码中的完整实现还会根据 component type、instant app、system/root/shell 和 shared user 处理更多分支;这里最重要的结构是:先处理空状态和 user install,再把 package visibility 交给 AppsFilterSnapshot,最后才按组件类型判断 component-level visibility。

ComputerEngine 不会把 AppsFilter 的图算法复制一份。它传入自身作为 snapshot,使 AppsFilter 在同一个包状态视图上计算 caller 与 target 的关系。

6.3 UID反查 ​

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

java
@Override
public String[] getPackagesForUid(int uid) {
    return getPackagesForUidInternal(uid, Binder.getCallingUid());
}

private String[] getPackagesForUidInternal(int uid, int callingUid) {
    final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
    final int userId = UserHandle.getUserId(uid);
    if (Process.isSdkSandboxUid(uid)) {
        uid = getBaseSdkSandboxUid();
    }
    if (isKnownIsolatedComputeApp(uid)) {
        uid = getIsolatedOwner(uid);
    }
    final int appId = UserHandle.getAppId(uid);
    return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
}

protected String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId,
        boolean isCallerInstantApp) {
    final Object obj = mSettings.getSettingBase(appId);
    if (obj instanceof SharedUserSetting) {
        if (isCallerInstantApp) {
            return null;
        }
        final SharedUserSetting sus = (SharedUserSetting) obj;
        final ArraySet<PackageStateInternal> packageStates =
                (ArraySet<PackageStateInternal>) sus.getPackageStates();
        String[] res = new String[packageStates.size()];
        int i = 0;
        for (PackageStateInternal ps : packageStates) {
            if (ps.getUserStateOrDefault(userId).isInstalled()
                    && !shouldFilterApplication(ps, callingUid, userId)) {
                res[i++] = ps.getPackageName();
            }
        }
        return ArrayUtils.trimToSize(res, i);
    } else if (obj instanceof PackageStateInternal) {
        final PackageStateInternal ps = (PackageStateInternal) obj;
        if (ps.getUserStateOrDefault(userId).isInstalled()
                && !shouldFilterApplication(ps, callingUid, userId)) {
            return new String[]{ps.getPackageName()};
        }
    }
    return null;
}

UID 反查先处理 SDK sandbox 和 isolated compute UID,再按 appId 找 SharedUserSetting 或 PackageStateInternal。shared UID 只返回当前 user 已安装且未被过滤的成员;instant app caller 直接得到 null。普通 UID 返回单元素数组,不可见或未安装时返回 null。源码还特别说明该 Binder API 有缓存,算法或底层 setting 变化时必须同步检查缓存失效点。

7. ComputerLocked ​

7.1 这个子类做什么 ​

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

java
/**
 * This subclass is the external interface to the live computer.  Some internal helper
 * methods are overridden to fetch live data instead of snapshot data.
 */
public final class ComputerLocked extends ComputerEngine {

    ComputerLocked(PackageManagerService.Snapshot args) {
        super(args, -1);
    }

    protected ComponentName resolveComponentName() {
        return mService.getResolveComponentName();
    }

    protected ActivityInfo instantAppInstallerActivity() {
        return mService.mInstantAppInstallerActivity;
    }

    protected ApplicationInfo androidApplication() {
        return mService.getCoreAndroidApplication();
    }
}

Android 17 的 ComputerLocked 并没有重写大量查询方法,它只替换三个 protected accessor:resolve component、instant app installer activity 和 core Android application。构造时 version 为 -1,表示这是 live computer,而不是普通编号 snapshot。

7.2 Live边界 ​

Computer 注释要求:只有读取 live data 不安全时才在 ComputerLocked 覆盖,并且覆盖函数只能在锁下调用基类实现。原因有两面:

  • 不覆盖时,查询直接走稳定快照,读线程不用持有 PMS lock;
  • 过度覆盖时,查询可能遍历正在修改的 resolver、Settings 或 per-user 状态,读到不一致集合。

ComputerLocked 的存在不是“性能更快的 Computer”,而是为少数必须观察当前 live 属性的内部路径提供一个受锁保护的入口。任何新方法都应先证明 snapshot 数据不足,再考虑覆盖。

8. Snapshot入口 ​

8.1 创建引擎 ​

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

java
@GuardedBy("mLock")
private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
    var now = SystemClock.currentTimeMicro();
    var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
    var args = new Snapshot(Snapshot.SNAPPED);
    var newSnapshot = new ComputerEngine(args, newVersion);
    var done = SystemClock.currentTimeMicro();

    if (mSnapshotStatistics != null) {
        mSnapshotStatistics.rebuild(now, done, hits,
                newSnapshot.getPackageStates().size());
    }
    return newSnapshot;
}

/** Create a live computer. */
private ComputerLocked createLiveComputer() {
    return new ComputerLocked(new Snapshot(Snapshot.LIVE));
}

rebuildSnapshot() 在 PMS lock 下创建 Snapshot.SNAPPED 参数,再交给 ComputerEngine 保存字段。createLiveComputer() 使用 Snapshot.LIVE 和 ComputerLocked。查询引擎本身不知道 snapshot 何时失效、何时发布;这些属于 PMS 的缓存管理逻辑。

8.2 快照与查询的时序 ​

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

java
@NonNull
public Computer snapshotComputer() {
    return snapshotComputer(true /*allowLiveComputer*/);
}

if (oldSnapshot != null && oldSnapshot.getVersion() == pendingVersion) {
    return oldSnapshot.use();
}

调用方拿到的是一个已经选择好的 Computer。之后 ComputerEngine 只在这个对象上读数据;如果中途 PMS 状态变化,当前查询不会自动切换到新版本。下一篇会详细展开版本失效和重建竞争,本篇只需记住:一个查询链应尽量复用同一个 Computer,避免同一业务在两个版本间拼接结果。

9. 共享库与权限查询 ​

9.1 共享库使用者查询 ​

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

java
@Override
public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
        @NonNull SharedLibraryInfo libInfo, long flags,
        int callingUid, @UserIdInt int userId) {
    List<VersionedPackage> versionedPackages = null;
    final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates();
    final int packageCount = packageStates.size();
    List<Boolean> usesLibsOptional = null;
    for (int i = 0; i < packageCount; i++) {
        PackageStateInternal ps = packageStates.valueAt(i);
        if (ps == null) {
            continue;
        }
        if (!PackageUserStateUtils.isAvailable(ps.getUserStateOrDefault(userId), flags)) {
            continue;
        }
        final String libName = libInfo.getName();
        if (libInfo.isStatic() || libInfo.isSdk()) {
            final String[] libs = libInfo.isStatic()
                    ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries();
            final long[] libsVersions = libInfo.isStatic()
                    ? ps.getUsesStaticLibrariesVersions()
                    : ps.getUsesSdkLibrariesVersionsMajor();
            final boolean[] libsOptional = libInfo.isSdk()
                    ? ps.getUsesSdkLibrariesOptional() : null;
            final int libIdx = ArrayUtils.indexOf(libs, libName);
            if (libIdx < 0 || libsVersions[libIdx] != libInfo.getLongVersion()) {
                continue;
            }
            if (shouldFilterApplication(ps, callingUid, userId)) {
                continue;
            }
            if (versionedPackages == null) {
                versionedPackages = new ArrayList<>();
                usesLibsOptional = new ArrayList<>();
            }
            String dependentPackageName = ps.getPackageName();
            if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) {
                dependentPackageName = ps.getPkg().getManifestPackageName();
            }
            versionedPackages.add(new VersionedPackage(dependentPackageName, ps.getVersionCode()));
            usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]);
        } else if (ps.getPkg() != null) {
            if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName)
                    || ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) {
                if (shouldFilterApplication(ps, callingUid, userId)) {
                    continue;
                }
                if (versionedPackages == null) {
                    versionedPackages = new ArrayList<>();
                }
                versionedPackages.add(new VersionedPackage(ps.getPackageName(),
                        ps.getVersionCode()));
            }
        }
    }
    return Pair.create(versionedPackages, usesLibsOptional);
}

共享库查询展示了 ComputerEngine 的典型“状态扫描”模式:先按 user availability 过滤,再按库类型、名称和版本匹配,最后按 calling UID 做 visibility 过滤。即使调用者知道某包声明了库,也不能绕过 package visibility。

9.2 权限查询 ​

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

java
@Override
public int checkUidPermission(String permName, int uid) {
    return mPermissionManager.checkUidPermission(permName, uid);
}

@Override
public String[] getAppOpPermissionPackages(@NonNull String permissionName, int userId) {
    final int callingUid = Binder.getCallingUid();
    enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
            false /*checkShell*/, "getAppOpPermissionPackages");
    if (permissionName == null || getInstantAppPackageName(callingUid) != null
            || !mUserManager.exists(userId)) {
        return EmptyArray.STRING;
    }

    final ArraySet<String> packageNames = new ArraySet(
            mPermissionManager.getAppOpPermissionPackages(permissionName));
    for (int i = packageNames.size() - 1; i >= 0; i--) {
        final String packageName = packageNames.valueAt(i);
        if (!shouldFilterApplicationIncludingUninstalled(
                mSettings.getPackage(packageName), callingUid, userId)) {
            continue;
        }
        packageNames.removeAt(i);
    }
    return packageNames.toArray(new String[packageNames.size()]);
}

权限本身由 PermissionManagerServiceInternal owner 计算,ComputerEngine 只负责调用边界、user 检查和 visibility 二次过滤。这个分工避免权限表和包可见性表互相复制,同时确保返回的 package name 不暴露不可见包。

10. 消费者与调用约束 ​

10.1 PMS Binder 路径 ​

IPackageManagerBase 中的简单 Binder 方法通常是:

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

java
@Override
@Deprecated
public final PackageInfo getPackageInfo(String packageName,
        long flags, int filterCallingUid, int userId) {
    return snapshot().getPackageInfoInternal(packageName,
            PackageManager.VERSION_CODE_HIGHEST, flags, filterCallingUid, userId);
}

从 Binder 进入后,Base 取 Computer,再把 package、flags、filter UID 和 user 传给引擎。这里没有把 IPackageManager 的 calling UID 直接交给 ComputerEngine 的 Binder.getCallingUid() 替代,因为 Base 已经明确保留了 filterCallingUid 语义。

10.2 内部路径 ​

PermissionPolicyService 等本地服务通过 PackageManagerInternal 调用 snapshot() 或便利代理。典型代码是:

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

java
final Computer snapshot = mPackageManagerInternal.snapshot();
final AndroidPackage pkg = snapshot.getPackage(packageName);
final PackageStateInternal state = snapshot.getPackageStateInternal(packageName);

相较于连续调用 mPackageManagerInternal.getPackage()、getPackageStateInternal(),显式 snapshot 能保证两次读取来自同一查询视图。它仍不会让外部服务获得 PMS 写锁,也不会把内部对象变成线程安全可变对象;调用方必须遵循只读和生命周期约束。

10.3 典型错误用法 ​

以下写法会让查询链失去一致性:

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

java
// 不推荐:每次方法调用都可能取得不同版本的 Computer。
AndroidPackage pkg = pmi.getPackage(packageName);
PackageInfo info = pmi.getPackageInfo(packageName, flags, callingUid, userId);

如果 package 在两次调用之间更新,pkg 和 info 可能来自不同 snapshot;如果调用方再把它们写入自己的缓存,就会形成跨版本组合。更稳妥的写法是:

java
Computer snapshot = pmi.snapshot();
AndroidPackage pkg = snapshot.getPackage(packageName);
PackageInfo info = snapshot.getPackageInfoInternal(
        packageName, PackageManager.VERSION_CODE_HIGHEST, flags, callingUid, userId);

11. 失败与边界 ​

11.1 返回空列表的条件 ​

ComputerEngine 大量查询使用空列表表达“没有结果”,但原因可能不同:

条件典型方法语义
user 不存在queryIntentActivitiesInternal、getInstalledApplications参数指向无效 user,未进入匹配
instant app caller应用列表、共享库列表instant app 被禁止枚举全局包
package 不可见getApplicationInfoInternal、UID 反查隐藏目标,避免存在性侧信道
没有已安装 stateinstalled applications 普通分支状态存在但当前 user 未安装
没有解析包getPackage()、对象生成只有 Settings 状态,无法生成 manifest 对象

调试时应结合 flags、user state 和 calling UID 判断原因,不能只记录“返回空”。

11.2 异常与哨兵值 ​

不同 API 对失败的契约不同:

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

java
@Override
public long getCeDataInode(String packageName, int userId) {
    final PackageStateInternal packageState =
            getPackageStateInternal(packageName);
    if (packageState == null) {
        return 0;
    }
    return packageState.getUserStateOrDefault(userId).getCeDataInode();
}

@Override
public boolean wasPackageEverLaunched(String packageName, int userId) {
    final PackageStateInternal packageState = getPackageStateInternal(packageName);
    if (packageState == null) {
        throw new IllegalArgumentException("Unknown package: " + packageName);
    }
    return !packageState.getUserStateOrDefault(userId).isNotLaunched();
}

inode 查询用 0 表达错误或不存在,启动历史查询则抛 IllegalArgumentException。同一个引擎中的方法也不能统一套用“空值表示失败”或“异常表示失败”的规则。

11.3 查询不负责写回 ​

ComputerEngine 的接口约束禁止在查询函数内修改 snapshot 数据。查询可能触发少量统计,例如 use() 计数,但不会把 enabled、suspended、preferred 或 permission state 写回 PMS。若解析过程中发现 dangling preferred activity,清理动作由更高层 helper 在允许 mutation 的路径完成,而不是由普通 Computer 查询悄悄修改 Settings。

12. 测试证据 ​

12.1 AppsFilterTest ​

测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java

java
assertFalse(appsFilter.shouldFilterApplication(
        mSnapshot, DUMMY_CALLING_APPID, calling, target, USER_ID));
assertTrue(appsFilter.shouldFilterApplication(
        mSnapshot, DUMMY_CALLING_APPID, calling, target, USER_ID));
watcher.verifyNoChangeReported("shouldFilterApplication");

这类测试准备 calling/target package state 与一个 snapshot,然后改变签名、queryable 关系或组件条件,断言 shouldFilterApplication() 的 true/false,并确认查询没有修改 watcher 观察到的状态。它证明的是 AppsFilter 的纯判断和可见性规则,不证明 ComputerEngine 的 Intent 候选排序、PMS snapshot 重建或跨 user 权限。

12.2 ParserTest ​

测试文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java

java
pkg.hideAsFinal();
ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
        pkg, 0, PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(pkg));
assertNotNull(appInfo);

输入是一个解析完成的 package 和默认 user state,动作是调用 PackageInfoUtils.generateApplicationInfo(),断言生成结果非空。它覆盖“包模型 + user state 能生成 ApplicationInfo”,但没有覆盖 ComputerEngine 在生成前做的 shouldFilterApplication(),也没有覆盖 resolveExternalPackageName() 的外部名称转换。

12.3 该补什么测试 ​

如果要为新 ComputerEngine 查询增加测试,至少应拆成三层:

  1. 候选层:给定 package/component 集合,确认 resolver 返回哪些原始候选。
  2. 策略层:改变 user state、calling UID、instant app 标志,确认可见性和 cross-profile 过滤。
  3. 对象层:确认 PackageInfoUtils 生成的 ApplicationInfo/ResolveInfo 字段和外部 package name。

还应明确未证明的部分:snapshot 重建竞争、真实 Binder identity、APEX 扫描状态、磁盘持久化和安装卸载事件需要更高层测试,不能用单独的 PackageInfoUtils 或 AppsFilter 单测替代。

13. 阅读路线 ​

面对 ComputerEngine.java 中近六千行代码,可以固定使用下面的源码阅读顺序:

  1. 先在 Computer.java 找方法签名和 Javadoc,记录 user、calling UID、flags 以及返回值的失败语义。
  2. 在 ComputerEngine 找同名实现,先读 guard 条件,再读候选收集,最后读对象生成和 post filter。
  3. 遇到 mSettings,进入 Settings/PackageStateInternal;遇到 mComponentResolver,进入 resolver snapshot;遇到 mAppsFilter,进入 AppsFilter snapshot。
  4. 对 Intent 方法,按 updateFlagsForResolve → ComponentResolver → cross-profile → instant app → post filter 顺序画调用图。
  5. 对列表方法,区分 PackageStateInternal 集合与 mPackages 集合,再记录每个 flags 分支是否包含未安装、归档和 APEX。
  6. 检查 ComputerLocked 是否覆盖该方法;若没有,默认按稳定 snapshot 读取,若有,继续确认覆盖是否只做 live accessor 或锁包装。
  7. 最后读测试,把输入、动作、断言和未覆盖范围分别写出来。

小结 ​

Android 17 的 ComputerEngine 不是一个简单的“包查询工具类”,而是一套有明确边界的只读计算引擎:

  • Computer 要求查询函数对 snapshot 数据保持纯读,新函数先落在 ComputerEngine,只有 live data 不安全时才由 ComputerLocked 覆盖。
  • 构造函数只复制需要稳定读取的 Settings、包、resolver、AppsFilter 等数据;权限、UserManager、Context 等服务保持外部引用。
  • Intent 查询不是一次 resolver 调用,而是 user/flags guard、selector 处理、显式或隐式候选收集、跨 profile、instant app、原始 Intent 复核和 post filter 的连续链路。
  • 应用列表根据 MATCH_KNOWN_PACKAGES、MATCH_ARCHIVED_PACKAGES 和 MATCH_APEX 分支遍历 package state,再经过 availability、shared library、visibility 和对象生成。
  • shouldFilterApplication() 既处理包可见性,也处理未安装、归档、shared UID 和 component-level visibility;flags 扩大候选范围,但不会自动取消这些策略。
  • 查询返回空列表、null、哨兵值或异常各有具体契约,必须回到方法实现判断原因。

继续阅读时,最自然的下一步是进入 Computer 的 snapshot 产生与失效机制:同一个 ComputerEngine 如何被缓存、何时被 onChange() 标记过期、ComputerLocked 又怎样与 PMS lock 协作。这些内容属于并发与快照专题,不应混在本篇查询算法中。