授予运行时权限
本文承接 运行时权限请求、权限类型 和 权限服务架构。上一文解释用户如何点击授权界面;本文从点击之后的服务端方法 grantRuntimePermission 开始,逐层追踪 caller UID、用户和设备、包可见性、permission 类型、fixed/restricted flags、AccessState 写入和 metrics。这里不讨论 PermissionController 的 UI 文案,也不把安装阶段的 PackageInstalledParams 当作 runtime grant API。
1. 调用链
1.1 门面与实现
源码文件:frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@Override
public void grantRuntimePermission(String packageName,
String permissionName, String persistentDeviceId,
int userId) {
mPermissionManagerServiceImpl.grantRuntimePermission(
packageName, permissionName,
persistentDeviceId, userId);
}Java 门面不直接写 flags,真实实现由 PermissionManagerServiceInterface 提供。在 Android 17 中,该接口由 PermissionService 实现,状态由 AccessCheckingService 的 AccessState 持有。
1.2 三类 caller
| 调用者 | 典型场景 | 进入接口 |
|---|---|---|
| PermissionController | 用户在授权 UI 点击允许 | Binder IPermissionManager |
| 系统策略 | 默认权限、设备策略、角色 | LocalService/implementation |
| Shell/测试 | pm grant 或测试 API | Binder,受 shell restriction |
所有调用者最终经过 PermissionService 的 caller permission、包可见性和 permission 类型检查;PermissionController 并不因为是系统组件就跳过目标包验证。
2. 调用者校验
2.1 用户与 shell
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
private fun setRuntimePermissionGranted(
packageName: String,
userId: Int,
permissionName: String,
deviceId: String,
isGranted: Boolean,
skipKillUid: Boolean = false,
revokeReason: String? = null,
) {
val methodName = if (isGranted) {
"grantRuntimePermission"
} else "revokeRuntimePermission"
if (!userManagerInternal.exists(userId)) {
Slog.w(LOG_TAG, "$methodName: Unknown user $userId")
return
}
permissionEnforcer.enforceCallingOrSelfCrossUserPermission(
userId,
enforceFullPermission = true,
enforceShellRestriction = true,
methodName,
)
val enforcedPermissionName = if (isGranted) {
Manifest.permission.GRANT_RUNTIME_PERMISSIONS
} else Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
context.enforceCallingOrSelfPermission(
enforcedPermissionName, methodName)授予与撤销使用不同的 manifest permission;跨用户检查还会对 shell 应用 restrictions。未知用户直接返回,不创建权限状态。deviceId 已由门面转换为 persistent ID,后续 flags 以设备维度读取。
2.2 目标包快照
val packageState: PackageState?
val permissionControllerPackageState: PackageState?
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
packageState = snapshot.filtered(callingUid, userId)
.use { it.getPackageState(packageName) }
permissionControllerPackageState = snapshot.getPackageState(
permissionControllerPackageName)
}
val androidPackage = packageState?.androidPackage
if (androidPackage == null) {
// 不区分“不存在”和“调用者不可见”,避免泄露包存在性
Slog.w(LOG_TAG, "$methodName: Unknown package $packageName")
return
}PermissionService 通过 filtered snapshot 查询目标包;不可见包和不存在包都返回 unknown,防止权限 API 成为包枚举通道。PermissionController 的 appId 另从 unfiltered snapshot 取得,用于 role permission 管理判断。
3. 权限类型
3.1 可变类型
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
service.mutateState {
setRuntimePermissionGranted(
packageState,
userId,
permissionName,
deviceId,
isGranted,
canManageRolePermission,
overridePolicyFixed,
reportError = true,
methodName,
)
}真正写入发生在 service.mutateState 事务中。事务内的 setRuntimePermissionGranted 允许 development、role 和 runtime permission,其他 protection 类型进入不可变分支。
3.2 目标检查
val permission = with(policy) {
getPermissions()[permissionName]
}
if (permission == null) {
if (reportError) {
throw IllegalArgumentException(
"Unknown permission $permissionName")
}
return
}
when {
permission.isDevelopment -> Unit
permission.isRole -> {
if (!canManageRolePermission) {
if (reportError) {
throw SecurityException(
"Permission $permissionName is managed by role")
}
return
}
}
permission.isRuntime -> {
if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
return
}
}
else -> {
if (reportError) {
throw SecurityException(
"Permission $permissionName is not changeable")
}
return
}
}grantRuntimePermission 名称并不意味着所有 permission 都可变。development、role、dangerous/runtime 才能进入该函数;normal、signature、internal 等应在安装/策略评估阶段决定。targetSdk < M 的 legacy 包直接返回,因为其权限模型不是 runtime grant。
3.3 Instant app
if (isGranted &&
packageState.getUserStateOrDefault(userId).isInstantApp &&
!permission.isInstant
) {
throw SecurityException(
"Cannot grant non-instant permission $permissionName " +
"to package $packageName")
}instant app 只能获得带 PROTECTION_FLAG_INSTANT 的权限。即使 caller 拥有 grant permission,目标包的用户状态仍会限制可授予集合。
4. 请求与 fixed
4.1 请求声明
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
val appId = packageState.appId
val oldFlags = getPermissionFlagsWithPolicy(
appId, userId, permissionName, deviceId)
if (permissionName !in androidPackage.requestedPermissions
&& oldFlags == 0
) {
if (reportError) {
Slog.e(LOG_TAG,
"Permission $permissionName isn't requested by " +
"package $packageName")
}
return
}目标 APK 未在 Manifest 请求该 permission 且没有既有状态时,grant 被忽略。保留旧 flags 的包在某些迁移/兼容场景仍可处理,但不能把任意权限写入任意 appId。
4.2 固定状态
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
if (reportError) {
Slog.e(LOG_TAG,
"$methodName: Cannot change system fixed permission " +
"$permissionName for package $packageName")
}
return
}
if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)
&& !overridePolicyFixed
) {
if (reportError) {
Slog.e(LOG_TAG,
"$methodName: Cannot change policy fixed permission " +
"$permissionName for package $packageName")
}
return
}SYSTEM_FIXED 永远不能被该 API 修改;POLICY_FIXED 只有 caller 同时具备 ADJUST_RUNTIME_PERMISSIONS_POLICY 时才能覆盖。返回通常是无异常的 no-op,并由 PermissionController 根据后续查询显示最终状态。
5. restricted 权限
5.1 授予阻断
if (isGranted &&
oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)
) {
if (reportError) {
Slog.e(LOG_TAG,
"$methodName: Cannot grant hard-restricted " +
"non-exempt permission $permissionName")
}
return
}
if (isGranted &&
oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)
) {
if (reportError) {
Slog.e(LOG_TAG,
"$methodName: Cannot grant soft-restricted " +
"non-exempt permission $permissionName")
}
return
}hard/soft restricted permission 没有 exemption 时,即使是 runtime 类型也不能由普通 grant API 强行授予。exemption 通常来自 installer allowlist、系统策略或预装身份,先由安装/策略阶段写入 flags。
5.2 更新 flags
val newFlags = PermissionFlags.updateRuntimePermissionGranted(
oldFlags, isGranted)
if (oldFlags == newFlags) {
return
}
setPermissionFlagsWithPolicy(
appId, userId, permissionName,
deviceId, newFlags)updateRuntimePermissionGranted 只改变 runtime grant 来源位,保留 user-set、policy-fixed、restriction 等其他 bits。新旧 flags 相同则不写盘、不发变更通知,避免重复 grant 产生无意义的 UID 操作。
6. 生效与进程
6.1 状态提交
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
internal inline fun mutateState(
crossinline action: MutateStateScope.() -> Unit
) {
synchronized(stateLock) {
val oldState = state
val newState = oldState.toMutable()
MutateStateScope(oldState, newState).action()
persistence.write(newState)
state = newState
with(policy) {
GetStateScope(newState).onStateMutated()
}
}
}事务完成后才替换 volatile state 引用,读者不会看到半写入 flags。persistence.write 位于引用替换前,表示该实现把持久化纳入 mutation 临界区;具体文件写入方式由 AccessPersistence 决定。
6.2 metrics 与 UID
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
if (permission.isRuntime) {
val action = if (isGranted) {
MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
} else {
MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
}
val log = LogMaker(action).apply {
setPackageName(packageName)
addTaggedData(
MetricsProto.MetricsEvent.FIELD_PERMISSION,
permissionName,
)
}
metricsLogger.write(log)
}Android 17 的 PermissionService 在 flags 变化后记录 metrics。进程是否被杀死由 onPermissionFlagsChangedListener 和 revoke 路径策略处理;测试专用 revokePostNotificationPermissionWithoutKillForTest 可跳过 kill,普通 revoke 不能据此推断进程仍可使用旧能力。
7. 设备与 AppOps
7.1 设备 ID
Binder 门面把 numeric deviceId 转成 persistent device ID;PermissionService 以该字符串读取 permission flags。相同 appId/user 在不同虚拟设备上的授权可以独立存在,默认物理设备使用 default persistent ID。
7.2 AppOp permission
如果 permission 带 PROTECTION_FLAG_APPOP,grant runtime flags 只完成 permission 层授予;真实 API 还要由 PermissionCheckerService 查询 AppOps mode。MODE_ALLOWED、MODE_IGNORED 和 MODE_ERRORED 可以让同一个 permission grant 得到不同的数据访问结果。
8. 失败路径
8.1 典型失败
| 条件 | 行为 | 状态是否改变 |
|---|---|---|
| 用户不存在 | 记录 warning 并返回 | 否 |
| 目标包不可见/不存在 | 返回 unknown package | 否 |
| 权限未定义 | IllegalArgumentException | 否 |
| 未声明权限 | 记录错误并返回 | 否 |
| system fixed | 记录错误并返回 | 否 |
| policy fixed 且无 policy 权限 | 记录错误并返回 | 否 |
| hard/soft restricted 无 exemption | 记录错误并返回 | 否 |
| instant app 请求非 instant 权限 | SecurityException | 否 |
错误发生在 setPermissionFlagsWithPolicy 之前,因此失败不会产生新的 runtime grant。调用方若只看到 API 返回而不重新查询 flags,可能把 no-op 误认为成功。
8.2 角色权限
role permission 只有 root/system 或 PermissionController appId 能管理。其他 caller 即使拥有 GRANT_RUNTIME_PERMISSIONS,也会因 role owner 检查被拒绝;角色变化应通过 RoleManager/PermissionController 处理。
9. 验证方法
9.1 正常授予
安装 targetSdk >= 23、声明 CAMERA 的测试包,用 PermissionController 授予后调用 checkSelfPermission。断言 user 0 的 runtime flags 出现 grant,另一个用户不变;对未声明权限授予应无状态变化。
9.2 固定限制
为权限设置 system-fixed、policy-fixed 和 restriction revoked flags,分别调用 grant。预期 system-fixed 永不改变,policy-fixed 只有 policy caller 可改,restricted 无 exemption 时 grant 被拒绝。检查日志中的 methodName 和 package/permission,确认失败发生在 flags 写入前。
9.3 device-aware
在两个虚拟设备上授予同一包权限,分别用 deviceId 查询。断言 persistent device ID 对应的状态互不覆盖;默认设备仍使用 default ID。
9.4 AppOps
保持 permission grant,分别设置关联 AppOp 为 allowed、ignored、errored,调用真实受保护 API 和 PermissionChecker。对比静态 checkSelfPermission 与数据访问结果,验证 grant 不是最终资源访问保证。
9.5 进程生效
在应用进程运行时撤销权限,观察 UID 是否被终止并重新启动;使用测试专用 no-kill API 对照。授予路径不应依赖杀进程才能让后续检查看到新 flags。
adb shell pm grant <package.name> <permission>
adb shell pm revoke <package.name> <permission>
adb shell dumpsys package <package.name>
adb shell appops get <package.name>10. 源码路线
PermissionManagerService.grantRuntimePermission:确认 Binder 门面。PermissionService.setRuntimePermissionGranted:读取完整 caller、包和用户检查。- permission type/fixed/restricted 分支:理解哪些请求会 no-op 或抛异常。
PermissionFlags.updateRuntimePermissionGranted:跟踪 bits 如何变化。AccessCheckingService.mutateState:理解 copy-on-write、持久化和可见性。PermissionCheckerService:连接 AppOps 和数据访问。killUid/permission change listener:确认进程生效与撤销时机。
grantRuntimePermission 的核心不是把一个 bit 设为 1,而是先证明 caller 有权为目标 user/device 的目标包改变目标 permission,再在 AccessState 事务中只更新允许变化的 runtime 来源位。fixed、restricted、instant、role、AppOps 和包声明都会截断这条路径;只有把“请求、状态写入、进程生效、真实资源检查”分开,才能准确解释授予 API 返回后应用仍被拒绝的情况。
