Skip to content

撤销运行时权限

追踪 Android 17 权限撤销的校验、flags 变化、策略固定、进程生效和自动撤销入口。

基于android-17.0.0_r1
AndroidPermissionServicerevokeRuntimePermissionAppOps源码阅读

撤销运行时权限 ​

本文承接 授予运行时权限、运行时权限请求 和 权限服务架构。主题是 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

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

kotlin
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

kotlin
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 包可见性 ​

kotlin
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

kotlin
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 请求检查 ​

kotlin
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 ​

kotlin
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

kotlin
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

kotlin
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

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 ​

kotlin
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否
未声明 permissionerror/return否
normal/signature/internal不可变类型否
SYSTEM_FIXED拒绝否
POLICY_FIXED 无调整权限拒绝否
role 非管理 callerSecurityException/return否
旧 flags 无 grantno-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 不重复发生。

bash
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. 源码路线 ​

  1. PermissionManagerService.revokeRuntimePermission:确认 Java 门面。
  2. PermissionService.setRuntimePermissionGranted:按 caller、包、用户、类型和 fixed flags 阅读。
  3. PermissionFlags.updateRuntimePermissionGranted:理解 revoke 清除的来源位。
  4. AccessCheckingService.mutateState:追踪事务和持久化。
  5. killUid 与 permission change listener:确认进程生效。
  6. One-time、auto-revoke 和测试 no-kill:比较不同触发者如何复用同一函数。

撤销运行时权限不是把布尔值改成 false:服务先证明 caller 能为目标 user/device 的目标包修改目标 permission,再在 AccessState 事务中清除 RUNTIME_GRANTED,最后通过 UID 生效和监听/持久化传播变化。固定、restricted、role、AppOps 和 no-op 分支共同决定一次 revoke 是否真正发生。