撤销运行时权限
本文承接 授予运行时权限、运行时权限请求 和 权限服务架构。主题是 Android 17 的 revokeRuntimePermission 服务端路径:caller、用户、包可见性、权限类型、fixed/restricted flags、AccessState 写入、UID 生效,以及一次性权限和测试 no-kill 如何复用同一实现。
1. 调用入口
1.1 Java 门面
源码文件:frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
public void revokeRuntimePermission(String packageName,
String permissionName, String persistentDeviceId,
int userId, String reason) {
mPermissionManagerServiceImpl.revokeRuntimePermission(
packageName, permissionName,
persistentDeviceId, userId, reason);
}Java 门面只把 Binder 参数交给 PermissionService。
1.2 共享核心
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
override fun revokeRuntimePermission(
packageName: String,
permissionName: String,
deviceId: String,
userId: Int,
reason: String?,
) {
setRuntimePermissionGranted(
packageName, userId, permissionName, deviceId,
isGranted = false,
revokeReason = reason,
)
}grant/revoke 共用 setRuntimePermissionGranted;撤销只改变 isGranted、reason 和默认 kill 行为。
2. 调用者校验
2.1 用户与权限
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
val methodName = "revokeRuntimePermission"
if (!userManagerInternal.exists(userId)) {
Slog.w(LOG_TAG, "$methodName: Unknown user $userId")
return
}
permissionEnforcer.enforceCallingOrSelfCrossUserPermission(
userId, enforceFullPermission = true,
enforceShellRestriction = true, methodName,
)
context.enforceCallingOrSelfPermission(
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
methodName,
)未知用户不创建状态;跨用户和 shell restriction 由统一 enforcer 处理。
2.2 包可见性
val packageState = packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.filtered(callingUid, userId).use {
it.getPackageState(packageName)
}
}
val androidPackage = packageState?.androidPackage
if (androidPackage == null) {
Slog.w(LOG_TAG, "$methodName: Unknown package $packageName")
return
}不可见包和不存在包统一返回 unknown,避免 revoke API 泄露包存在性。
3. 可撤销类型
3.1 类型分支
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
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
}
}normal、signature、internal 不属于普通 runtime revoke;role 仅由 root/system 或 PermissionController 管理。
3.2 请求检查
val oldFlags = getPermissionFlagsWithPolicy(
packageState.appId, userId, permissionName, deviceId)
if (permissionName !in androidPackage.requestedPermissions
&& oldFlags == 0) {
if (reportError) {
Slog.e(LOG_TAG, "Permission $permissionName isn't requested")
}
return
}未声明且没有历史状态时,撤销不会创建 denied 记录。
4. 固定与限制
4.1 Fixed flags
if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
if (reportError) Slog.e(LOG_TAG,
"$methodName: Cannot change system fixed permission")
return
}
if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)
&& !overridePolicyFixed) {
if (reportError) Slog.e(LOG_TAG,
"$methodName: Cannot change policy fixed permission")
return
}SYSTEM_FIXED 永远不可由普通 revoke 修改;POLICY_FIXED 需要 policy 调整权限。
4.2 Restricted 状态
撤销不需要 hard/soft restricted exemption,但会保留 restriction、user-set 和 policy-fixed 等历史位。未来 grant 仍可能被 restriction flags 阻止。
5. Flags 事务
5.1 清除授予位
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
const val RUNTIME_GRANTED = 1 shl 4
const val USER_SET = 1 shl 5
const val USER_FIXED = 1 shl 6
const val POLICY_FIXED = 1 shl 7
fun updateRuntimePermissionGranted(
oldFlags: Int, isGranted: Boolean
): Int = if (isGranted) {
oldFlags or RUNTIME_GRANTED
} else {
oldFlags and RUNTIME_GRANTED.inv()
}撤销只清除 RUNTIME_GRANTED,不抹掉用户和策略历史位。
5.2 Copy-on-write
源码文件: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() }
}
}flags 在事务内变化;旧快照继续可读,新引用在事务结束后一次替换。
6. UID 生效
6.1 killUid
源码文件:frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
public static void killUid(int appId, int userId, String reason) {
final long identity = Binder.clearCallingIdentity();
try {
final IActivityManager am = ActivityManager.getService();
if (am != null) {
try {
am.killUidForPermissionChange(appId, userId, reason);
} catch (RemoteException ignored) {
// system_server 同进程路径
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}撤销后 kill UID 让运行中的进程丢失撤销前的权限缓存和数据访问句柄,reason 进入 ActivityManager 退出诊断。
6.2 测试 no-kill
override fun revokePostNotificationPermissionWithoutKillForTest(
packageName: String, userId: Int,
) {
setRuntimePermissionGranted(
packageName, userId,
Manifest.permission.POST_NOTIFICATIONS,
VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
isGranted = false, skipKillUid = true,
)
}这是测试专用接口,不能推导普通 revoke 不杀进程。
7. 自动与一次性
7.1 One-time
OneTimePermissionUserManager 在超时、UID gone 或主动停止后调用同一 revoke implementation,只提供触发时机和 reason。
7.2 Auto-revoke
unused app 的自动撤销由 PermissionController/AppOps 策略触发,最终仍经过 revoke。SYSTEM_FIXED 不能被普通 unused 策略清除。
8. 失败矩阵
| 条件 | 结果 | flags 是否改变 |
|---|---|---|
| 未知用户 | warning/return | 否 |
| 包不可见/不存在 | unknown package | 否 |
| 未声明 permission | error/return | 否 |
| normal/signature/internal | 不可变类型 | 否 |
| SYSTEM_FIXED | 拒绝 | 否 |
| POLICY_FIXED 无调整权限 | 拒绝 | 否 |
| role 非管理 caller | SecurityException/return | 否 |
| 旧 flags 无 grant | no-op | 否 |
| runtime grant 存在 | 清除 RUNTIME_GRANTED | 是 |
9. 验证方法
9.1 用户撤销
授予 dangerous 权限后从设置撤销,检查 flags、checkSelfPermission 和进程。预期 grant 位清除,普通进程被 kill/restart,历史 USER_SET 等位按策略保留。
9.2 固定限制
设置 system-fixed、policy-fixed、hard restricted 后分别调用 revoke。预期 fixed 状态保持不变,policy caller 只可覆盖 policy fixed。
9.3 自动撤销
请求“仅本次”权限后等待超时或 UID gone,检查 revoke reason、flags 和进程状态;对 unused app 触发 auto-revoke,确认 fixed 权限不被普通策略撤销。
9.4 no-op
对已撤销权限重复调用 revoke,确认 flags、listener 和 UID kill 不重复发生。
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.revokeRuntimePermission:确认 Java 门面。PermissionService.setRuntimePermissionGranted:按 caller、包、用户、类型和 fixed flags 阅读。PermissionFlags.updateRuntimePermissionGranted:理解 revoke 清除的来源位。AccessCheckingService.mutateState:追踪事务和持久化。killUid与 permission change listener:确认进程生效。- One-time、auto-revoke 和测试 no-kill:比较不同触发者如何复用同一函数。
撤销运行时权限不是把布尔值改成 false:服务先证明 caller 能为目标 user/device 的目标包修改目标 permission,再在 AccessState 事务中清除 RUNTIME_GRANTED,最后通过 UID 生效和监听/持久化传播变化。固定、restricted、role、AppOps 和 no-op 分支共同决定一次 revoke 是否真正发生。
