Skip to content

静默安装

追踪 Android 17 静默安装的用户操作判定、权限组合、更新所有权、节流和运行时权限授予。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageInstallerSilentUpdatePolicy权限源码阅读

静默安装 ​

本文承接 Session 安装、安装前检查 和 StagedSession 暂存安装。主题不是“后台调用 PackageInstaller 就一定静默”,而是 Android 17 如何计算一次 session 是否需要用户操作,以及计算结果如何在 commit 前后影响流程。

静默判定分布在 PackageInstallerService.createSessionInternal()、PackageInstallerSession.computeUserActionRequirement()、checkUserActionRequirement() 和 SilentUpdatePolicy。INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS 也不是静默的同义词:它只控制安装时是否为请求权限生成 granted state,并且创建 session 时受到独立权限保护。

1. 判定主线 ​

1.1 判定入口 ​

computeUserActionRequirement() 只计算“通常是否需要用户操作”;checkUserActionRequirement() 再处理 pending APK parsing、target SDK change 和静默更新节流。只有两个方法都放行,session 才会继续 install() 或 staged 提交。

1.2 三种结果 ​

内部结果含义后续动作
USER_ACTION_REQUIRED必须弹确认发送 confirm intent 并暂停
USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER更新所有权提醒发送提醒型确认
USER_ACTION_PENDING_APK_PARSING解析 APK 后再决定检查 target SDK 和 throttle
USER_ACTION_NOT_NEEDED不需要用户操作继续安装

2. Session 创建保护 ​

2.1 grant-all 标志 ​

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

java
if (!params.isMultiPackage()) {
    final boolean hasInstallGrantRuntimePermissions = mContext
            .checkCallingOrSelfPermission(Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS)
            == PackageManager.PERMISSION_GRANTED;

    if ((params.installFlags & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0
            && !hasInstallGrantRuntimePermissions) {
        throw new SecurityException("You need the "
                + Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS
                + " permission to use the"
                + " PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS flag");
    }
}

只有非 multi-package session 在这里检查 INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS;调用者必须有 INSTALL_GRANT_RUNTIME_PERMISSIONS。这一步失败时 session 不会进入写入和用户操作判定。

2.2 Permission state ​

java
var permissionStates = params.getPermissionStates();
if (!permissionStates.isEmpty()) {
    if (!hasInstallGrantRuntimePermissions) {
        for (int index = 0; index < permissionStates.size(); index++) {
            var permissionName = permissionStates.keyAt(index);
            if (!INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS.contains(permissionName)) {
                throw new SecurityException("You need the "
                        + Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS
                        + " permission to grant runtime permissions for a session");
            }
        }
    }
}

即使不使用 grant-all,session 也可以携带逐项 permission state;没有高级权限时,只允许修改白名单中的 AppOp 权限。静默安装和权限授予是两个正交检查。

3. 用户操作计算 ​

3.1 强制提示与权限 ​

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

java
final boolean forceUserActionPrompt =
        (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0
                || params.requireUserAction == SessionParams.USER_ACTION_REQUIRED;
final int userActionNotTypicallyNeededResponse = forceUserActionPrompt
        ? USER_ACTION_REQUIRED : USER_ACTION_NOT_NEEDED;

final Computer snapshot = mPm.snapshotComputer();
final boolean isInstallPermissionGranted = snapshot.checkUidPermission(
        Manifest.permission.INSTALL_PACKAGES, mInstallerUid)
        == PackageManager.PERMISSION_GRANTED;
final boolean isSelfUpdatePermissionGranted = snapshot.checkUidPermission(
        Manifest.permission.INSTALL_SELF_UPDATES, mInstallerUid)
        == PackageManager.PERMISSION_GRANTED;
final boolean isUpdatePermissionGranted = snapshot.checkUidPermission(
        Manifest.permission.INSTALL_PACKAGE_UPDATES, mInstallerUid)
        == PackageManager.PERMISSION_GRANTED;
final boolean isUpdateWithoutUserActionPermissionGranted = snapshot.checkUidPermission(
        Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION, mInstallerUid)
        == PackageManager.PERMISSION_GRANTED;

显式 USER_ACTION_REQUIRED 或 INSTALL_FORCE_PERMISSION_PROMPT 会把通常无需提示的结果升级为 required。权限读取则区分全量安装、更新、自更新和“无用户操作更新”四类能力。

3.2 更新与安装器身份 ​

java
final int targetPackageUid = snapshot.getPackageUid(
        packageName, PackageManager.MATCH_ARCHIVED_PACKAGES, userId);
final boolean isUpdate = targetPackageUid != -1 || isApexSession();
final InstallSourceInfo existingInstallSourceInfo = isUpdate
        ? snapshot.getInstallSourceInfo(packageName, userId) : null;
final String existingInstallerPackageName = existingInstallSourceInfo != null
        ? existingInstallSourceInfo.getInstallingPackageName() : null;
final String existingUpdateOwnerPackageName = existingInstallSourceInfo != null
        ? existingInstallSourceInfo.getUpdateOwnerPackageName() : null;
final boolean isInstallerOfRecord = isUpdate
        && Objects.equals(existingInstallerPackageName, getInstallerPackageName());
final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName,
        getInstallerPackageName());
final boolean isSelfUpdate = targetPackageUid == mInstallerUid;

静默更新判定依赖当前包是否存在、原安装器和 update owner 是否匹配,以及 installer UID 是否就是目标包 UID。新安装没有 installer-of-record,更新则可能有。

3.3 权限组合 ​

java
final boolean isPermissionGranted = isInstallPermissionGranted
        || (isUpdatePermissionGranted && isUpdate)
        || (isSelfUpdatePermissionGranted && isSelfUpdate)
        || (isInstallDpcPackagesPermissionGranted && hasDeviceAdminReceiver)
        || (isInstallDependencyPackagesPermissionGranted && isSdkOrStaticLibraryInstall);

静默能力是多个条件的 OR:持有 INSTALL_PACKAGES,拥有更新权限且确实是更新,自更新权限且 UID 匹配,DPC 安装权限且包含 device-admin receiver,或依赖安装权限且目标是 SDK/static library。不能用一个权限名概括。

3.4 可信安装器 ​

java
final boolean noUserActionNecessary = isInstallerRoot
        || isInstallerSystem
        || isInstallerDeviceOwnerOrAffiliatedProfileOwner()
        || isEmergencyInstall
        || isInstallUnarchive;

if (noUserActionNecessary) {
    return userActionNotTypicallyNeededResponse;
}

root、system、设备所有者/关联 profile owner、emergency installer 和 unarchive 安装可以绕过普通权限判定;但如果 session 参数显式强制 prompt,返回值仍是 USER_ACTION_REQUIRED。

3.5 更新所有权提醒 ​

java
if (isUpdateOwnershipEnforcementEnabled
        && !isApexSession()
        && !isUpdateOwner
        && !isInstallerShell
        && !isFromManagedUserOrProfile) {
    return USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER;
}

启用 update ownership 时,非 owner installer 更新普通 APK 会触发提醒;Apex、shell 和 managed user/profile 有例外。这个结果不是普通权限失败,而是单独的用户操作原因。

3.6 最终计算 ​

java
if (isPermissionGranted) {
    return userActionNotTypicallyNeededResponse;
}
if (snapshot.isInstallDisabledForPackage(
        getInstallerPackageName(), mInstallerUid, userId)) {
    return USER_ACTION_REQUIRED;
}
if (params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED
        && isUpdateWithoutUserActionPermissionGranted
        && ((isUpdateOwnershipEnforcementEnabled ? isUpdateOwner
        : isInstallerOfRecord) || isSelfUpdate)) {
    return USER_ACTION_PENDING_APK_PARSING;
}
return USER_ACTION_REQUIRED;

没有足够权限时,设备策略可以强制 prompt;只有 session 明确要求 USER_ACTION_NOT_REQUIRED,且 installer 拥有 update-without-user-action 权限并满足 owner/self-update 条件,才进入 pending APK parsing。其它情况都需要用户操作。

4. Pending Parsing ​

4.1 检查入口 ​

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

java
private static boolean checkUserActionRequirement(
        PackageInstallerSession session, IntentSender target) {
    if (session.isMultiPackage()) {
        return false;
    }
    @UserActionRequirement int requirement =
            session.computeUserActionRequirement();
    session.updateUserActionRequirement(requirement);
    if (requirement == USER_ACTION_REQUIRED
            || requirement == USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER) {
        session.sendPendingUserActionIntent(target);
        return true;
    }

multi-package session 在这里直接返回 false,由 parent/child 安装协调统一处理;单包才进入 requirement 分支。计算结果写回 session,SessionInfo 可以对外报告 pending user action reason。

4.2 Target SDK ​

java
if (!session.isApexSession()
        && requirement == USER_ACTION_PENDING_APK_PARSING) {
    if (!isTargetSdkConditionSatisfied(session)) {
        session.sendPendingUserActionIntent(target);
        return true;
    }

pending 状态必须先检查目标 APK 的 target SDK 是否满足 SILENT_INSTALL_ALLOWED change。Apex 不经过这个 APK target SDK 分支。

4.3 Silent throttle ​

java
if (session.params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED) {
    if (!session.mSilentUpdatePolicy.isSilentUpdateAllowed(
            session.getInstallerPackageName(), session.getPackageName())) {
        // Fall back to non-silent update within throttle time.
        session.sendPendingUserActionIntent(target);
        return true;
    }
    session.mSilentUpdatePolicy.track(session.getInstallerPackageName(),
            session.getPackageName());
}

即使权限和 target SDK 都满足,重复静默更新仍可能被节流,系统会退回用户确认。只有真正放行时才 track() 当前 installer/package 时间戳。

5. Silent policy ​

5.1 时间戳模型 ​

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

java
private static final long SILENT_UPDATE_THROTTLE_TIME_MS =
        TimeUnit.SECONDS.toMillis(30);

@GuardedBy("mSilentUpdateInfos")
private final ArrayMap<Pair<String, String>, Long> mSilentUpdateInfos = new ArrayMap<>();

@GuardedBy("mSilentUpdateInfos")
private String mAllowUnlimitedSilentUpdatesInstaller;

@GuardedBy("mSilentUpdateInfos")
private long mSilentUpdateThrottleTimeMs = SILENT_UPDATE_THROTTLE_TIME_MS;

策略按 (installerPackageName, packageName) 保存 uptime 时间戳,默认节流 30 秒。它不是全局“每秒安装次数”计数器,而是每个安装器对每个目标包独立限流。

5.2 allowed 判断 ​

java
public boolean isSilentUpdateAllowed(@Nullable String installerPackageName,
        @NonNull String packageName) {
    if (installerPackageName == null) {
        // Always true for the installer from Shell.
        return true;
    }
    final long lastSilentUpdatedMs = getTimestampMs(installerPackageName, packageName);
    final long throttleTimeMs;
    synchronized (mSilentUpdateInfos) {
        throttleTimeMs = mSilentUpdateThrottleTimeMs;
    }
    return SystemClock.uptimeMillis() - lastSilentUpdatedMs > throttleTimeMs;
}

shell installer(package name 为 null)永远通过节流。普通安装器没有记录时 getTimestampMs() 返回 -1,因此首次更新一定通过;后续按 uptime 差值比较。

5.3 track 与 prune ​

java
public void track(@Nullable String installerPackageName, @NonNull String packageName) {
    if (installerPackageName == null) {
        return;
    }
    synchronized (mSilentUpdateInfos) {
        if (mAllowUnlimitedSilentUpdatesInstaller != null
                && mAllowUnlimitedSilentUpdatesInstaller.equals(installerPackageName)) {
            return;
        }
        final long uptime = SystemClock.uptimeMillis();
        pruneLocked(uptime);
        mSilentUpdateInfos.put(
                Pair.create(installerPackageName, packageName), uptime);
    }
}

成功放行后记录时间,并清除已超过当前节流时间的旧记录。白名单安装器不记录时间戳,避免无意义增长。

5.4 policy 配置 ​

java
void setAllowUnlimitedSilentUpdates(@Nullable String installerPackageName) {
    synchronized (mSilentUpdateInfos) {
        if (installerPackageName == null) {
            mSilentUpdateInfos.clear();
        }
        mAllowUnlimitedSilentUpdatesInstaller = installerPackageName;
    }
}

void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) {
    synchronized (mSilentUpdateInfos) {
        mSilentUpdateThrottleTimeMs = throttleTimeInSeconds >= 0
                ? TimeUnit.SECONDS.toMillis(throttleTimeInSeconds)
                : SILENT_UPDATE_THROTTLE_TIME_MS;
    }
}

配置接口使用秒作为输入;-1 恢复默认 30 秒。清除 unlimited installer 时同时清空 tracker;设置新白名单不会清除其它 installer 的记录。

6. 用户确认与继续 ​

6.1 confirm intent ​

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

java
private void sendPendingUserActionIntent(IntentSender target) {
    final boolean isPreapproval = isPreapprovalRequested() && !isCommitted();
    final Intent intent = new Intent(isPreapproval
            ? PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL
            : PackageInstaller.ACTION_CONFIRM_INSTALL);
    intent.setPackage(mPm.getPackageInstallerPackageName());
    intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
    sendOnUserActionRequired(mContext, target, sessionId, intent);
}

required 或 throttle 失败时,session 向 PackageInstaller 应用发送 confirm intent,并携带 sessionId。调用方确认后通过 setPermissionsResult() 等路径让 handler 重新检查,而不是直接跳过判定。

6.2 验证完成 ​

java
@WorkerThread
private void onVerificationComplete() {
    if (isStaged()) {
        mStagingManager.commitSession(mStagedSession);
        sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged",
                /*extras=*/ null, /*forPreapproval=*/ false);
        return;
    }
    install();
}

用户确认和验证完成后,普通 session 调用 install();staged session 仍只进入 StagingManager。静默判定不会改变 staged 的跨重启语义。

7. Grant-all ​

7.1 生成状态 ​

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

java
final PermissionManagerServiceInternal.PackageInstalledParams.Builder builder =
        new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
final boolean grantRequestedPermissions = (installRequest.getInstallFlags()
        & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0;
if (grantRequestedPermissions) {
    var permissionStates = new ArrayMap<String, Integer>();
    for (var permissionName : pkg.getRequestedPermissions()) {
        permissionStates.put(permissionName,
                PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED);
    }
    builder.setPermissionStates(permissionStates);
}

grant-all 在 commit 后处理阶段把 package 的 requested permissions 转为逐项 granted state,再交给 PermissionManager。它不负责决定是否弹用户确认;创建 session 时的权限保护和这里的状态生成是两件事。

7.2 状态 owner ​

内容owner时机
grant-all flagSessionParams.installFlagscreateSession/commit 前
permission state mapInstallRequest/PackageInstalledParamscommit 处理
最终授权状态PermissionManagerService安装后权限处理
用户操作需求PackageInstallerSessionverification 前后

8. 状态机与失败 ​

8.1 判定状态机 ​

8.2 失败边界 ​

失败点结果
创建 session 权限不足抛 SecurityException,不产生 session
强制用户操作发送 confirm intent,暂停
update owner 不匹配reminder intent
target SDK change 不满足confirm intent
silent throttle 未通过confirm intent,不是安装失败
用户拒绝session failed/abandoned,由 session 结束流程处理
PMS 安装阶段失败InstallRequest 错误,进入安装清理

静默条件不满足通常是“转为需要用户操作”,不是 APK 格式错误;只有后续用户拒绝、验证失败或 PMS 安装失败才产生终态失败。

9. 测试与诊断 ​

9.1 输入与断言 ​

输入断言证明范围
grant-all 无 runtime grant 权限createSession SecurityExceptionflag 保护
root/system installer通常不需要用户操作可信 caller
device owner/profile owner不弹确认管理员例外
USER_ACTION_REQUIRED必须发送 confirm intent强制提示
update owner 不匹配requirement 为 reminder所有权
update-without-user-action + ownerpending APK parsing延迟判定
target SDK change 关闭继续静默路径compatibility
target SDK 不满足confirm intenttarget gate
首次静默更新policy allowed 且记录 timestamptracker
30 秒内重复更新confirm intentthrottle
Shell installer不受 throttlenull installer
grant-all commit每个 requested permission 为 granted权限状态生成

测试要分别断言 requirement、pending reason、status receiver 和最终 permission state,不能只用“没有弹窗”证明静默安装成功。

9.2 现场排查顺序 ​

  1. 查看 SessionInfo 的 requireUserAction 和 pendingUserActionReason。
  2. 检查 installer UID 的 INSTALL/UPDATE/SELF_UPDATE/UPDATE_WITHOUT_USER_ACTION 权限。
  3. 判断目标包是否为 update、self-update、update owner 或 managed profile 场景。
  4. 对 pending parsing 检查 target SDK change 和 mValidatedTargetSdk。
  5. 查看 SilentUpdatePolicy.dump() 的 installer/package 时间戳与 throttle 秒数。
  6. 若已放行,检查 grant-all 是否只影响 PermissionManager,而不是用户操作判定。
  7. 若最终失败,转查 InstallRequest/PMS 安装阶段,不要把 confirm intent 当成失败。

10. 源码路线 ​

建议按以下顺序阅读:

  1. PackageInstallerService.createSessionInternal():grant-all 和 permission state 权限保护。
  2. PackageInstallerSession.computeUserActionRequirement():权限、caller、owner 和策略组合。
  3. checkUserActionRequirement():target SDK、pending parsing 和 throttle。
  4. SilentUpdatePolicy:时间戳、prune、unlimited installer 和配置。
  5. sendPendingUserActionIntent():确认 intent 和 sessionId。
  6. onVerificationComplete():普通/staged 安装分叉。
  7. InstallPackageHelper 的 grant-all 处理:requested permission 到 granted state。
  8. Session future/observer:用户确认、安装完成和失败状态。

11. 设计收束 ​

静默安装的真实链路是:

text
Session 创建权限
  -> 用户操作需求计算
  -> target SDK / update ownership / SilentUpdatePolicy
  -> confirm intent 或 install/stage
  -> grant-all 权限状态生成(独立路径)
  -> PMS 安装结果与 session 回调

静默不是一个布尔 flag,而是 caller 权限、目标包关系、session 参数、兼容性 change、节流策略和用户状态共同决定的结果。USER_ACTION_PENDING_APK_PARSING 表示“还不能决定”,SilentUpdatePolicy 失败表示退回确认而不是安装失败,INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS 则只影响权限状态。把这些 owner 和时机分开,才能准确解释同一个安装器为何首次更新静默、短时间重复更新弹窗,以及为什么授予权限的 flag 不会自动绕过用户操作策略。