静默安装
本文承接 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
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
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
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 更新与安装器身份
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 权限组合
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 可信安装器
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 更新所有权提醒
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 最终计算
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
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
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
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
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 判断
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
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 配置
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
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 验证完成
@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
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 flag | SessionParams.installFlags | createSession/commit 前 |
| permission state map | InstallRequest/PackageInstalledParams | commit 处理 |
| 最终授权状态 | PermissionManagerService | 安装后权限处理 |
| 用户操作需求 | PackageInstallerSession | verification 前后 |
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 SecurityException | flag 保护 |
| root/system installer | 通常不需要用户操作 | 可信 caller |
| device owner/profile owner | 不弹确认 | 管理员例外 |
USER_ACTION_REQUIRED | 必须发送 confirm intent | 强制提示 |
| update owner 不匹配 | requirement 为 reminder | 所有权 |
| update-without-user-action + owner | pending APK parsing | 延迟判定 |
| target SDK change 关闭 | 继续静默路径 | compatibility |
| target SDK 不满足 | confirm intent | target gate |
| 首次静默更新 | policy allowed 且记录 timestamp | tracker |
| 30 秒内重复更新 | confirm intent | throttle |
| Shell installer | 不受 throttle | null installer |
| grant-all commit | 每个 requested permission 为 granted | 权限状态生成 |
测试要分别断言 requirement、pending reason、status receiver 和最终 permission state,不能只用“没有弹窗”证明静默安装成功。
9.2 现场排查顺序
- 查看 SessionInfo 的
requireUserAction和pendingUserActionReason。 - 检查 installer UID 的 INSTALL/UPDATE/SELF_UPDATE/UPDATE_WITHOUT_USER_ACTION 权限。
- 判断目标包是否为 update、self-update、update owner 或 managed profile 场景。
- 对 pending parsing 检查 target SDK change 和
mValidatedTargetSdk。 - 查看
SilentUpdatePolicy.dump()的 installer/package 时间戳与 throttle 秒数。 - 若已放行,检查 grant-all 是否只影响 PermissionManager,而不是用户操作判定。
- 若最终失败,转查 InstallRequest/PMS 安装阶段,不要把 confirm intent 当成失败。
10. 源码路线
建议按以下顺序阅读:
PackageInstallerService.createSessionInternal():grant-all 和 permission state 权限保护。PackageInstallerSession.computeUserActionRequirement():权限、caller、owner 和策略组合。checkUserActionRequirement():target SDK、pending parsing 和 throttle。SilentUpdatePolicy:时间戳、prune、unlimited installer 和配置。sendPendingUserActionIntent():确认 intent 和 sessionId。onVerificationComplete():普通/staged 安装分叉。InstallPackageHelper的 grant-all 处理:requested permission 到 granted state。- Session future/observer:用户确认、安装完成和失败状态。
11. 设计收束
静默安装的真实链路是:
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 不会自动绕过用户操作策略。
