Skip to content

权限定义与声明

追踪 Manifest 权限、权限组和权限树进入 Android 17 权限状态的源码路径。

基于android-17.0.0_r1
AndroidPermissionManifestPermissionManagerService源码阅读

权限定义与声明 ​

本文承接 权限类型 和 权限服务架构。上一文解释 protection 与授权 flags;本文回到 Manifest 输入端,追踪 <permission>、<permission-group>、<permission-tree> 如何成为 AndroidPackage 的解析结果,并在包加入、更新、卸载时进入 AppIdPermissionPolicy。<uses-permission> 只是请求,不拥有权限定义。

1. Manifest 输入 ​

1.1 四种声明 ​

xml
<permission
    android:name="com.example.permission.READ_RECORD"
    android:protectionLevel="dangerous"
    android:permissionGroup="com.example.permission-group.RECORDS" />
<permission-group android:name="com.example.permission-group.RECORDS" />
<permission-tree android:name="com.example.permission" />
<uses-permission android:name="com.example.permission.READ_RECORD" />

permission 定义可被请求的权限;permission-group 提供分组元数据;permission-tree 定义动态权限前缀;uses-permission 进入 AndroidPackage.requestedPermissions。解析阶段只建立事实,是否授予由后续策略计算。

1.2 tree 匹配 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
fun GetStateScope.findPermissionTree(
    permissionName: String
): Permission? =
    state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
        _, treeName, tree ->
        if (permissionName.startsWith(treeName) &&
            permissionName.length > treeName.length &&
            permissionName[treeName.length] == '.'
        ) tree else null
    }

动态权限必须是 tree name 加点号作为前缀。相似前缀但没有点号的名称不属于该 tree,避免跨包创建权限。

2. 包加入 ​

2.1 生命周期入口 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
override fun MutateStateScope.onPackageAdded(
    packageState: PackageState
) {
    val changed = MutableIndexedSet<String>()
    adoptPermissions(packageState, changed)
    addPermissionGroups(packageState)
    addPermissions(packageState, changed)
    trimPermissions(packageState.packageName, changed)
    trimPermissionStates(packageState.appId)
    changed.forEachIndexed { _, name ->
        evaluatePermissionStateForAllPackages(name, null)
    }
    evaluateAllPermissionStatesForPackage(
        packageState, packageState
    )
    updatePermissionsOnPackageUpdate(packageState.appId)
}

包加入会先 adoption,再加入组和权限,裁剪过时定义,最后重新评估所有受影响 appId。一个包定义权限,可能改变其他包的 protection/请求关系,因此不能只更新自己的 map。

2.2 权限组 ​

kotlin
private fun MutateStateScope.addPermissionGroups(
    packageState: PackageState
) {
    val allUsersInstant = packageState.userStates.allIndexed {
        _, _, state -> state.isInstantApp
    }
    if (allUsersInstant) {
        Slog.w(LOG_TAG,
            "Ignoring permission groups declared by instant app")
        return
    }
    packageState.androidPackage!!.permissionGroups
        .forEachIndexed { _, parsed ->
            val info = PackageInfoUtils.generatePermissionGroupInfo(
                parsed, PackageManager.GET_META_DATA.toLong()
            )!!
            newState.mutateSystemState()
                .mutatePermissionGroups()[info.name] = info
        }
}

instant app 不能声明 permission group。组冲突时,普通包不能覆盖已有 owner;system 包可以覆盖非 system 定义,但两个 system 包之间仍拒绝后者。

3. 权限注册 ​

3.1 定义映射 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
private fun MutateStateScope.addPermissions(
    packageState: PackageState,
    changed: MutableIndexedSet<String>,
) {
    val pkg = packageState.androidPackage!!
    pkg.permissions.forEachIndexed { _, parsed ->
        val info = PackageInfoUtils.generatePermissionInfo(
            parsed, PackageManager.GET_META_DATA.toLong()
        )!!
        val old = if (parsed.isTree) {
            newState.systemState.permissionTrees[info.name]
        } else {
            newState.systemState.permissions[info.name]
        }
        val tree = findPermissionTree(info.name)
        if (tree != null && info.packageName != tree.packageName) {
            Slog.w(LOG_TAG,
                "Ignoring permission: tree owned by another package")
            return@forEachIndexed
        }
        // old owner、protection 和状态变化在后续逻辑中处理
    }
}

generatePermissionInfo 把解析对象转换成系统 definition。tree 和普通 permission 使用不同 map,但都必须先通过 tree owner 检查。

3.2 owner 冲突 ​

kotlin
if (old != null && info.packageName != old.packageName) {
    if (!packageState.isSystem) {
        Slog.w(LOG_TAG,
            "Ignoring permission already declared by another package")
        return@forEachIndexed
    }
    if (old.isReconciled &&
        newState.externalState.packageStates[
            old.packageName
        ]?.isSystem == true
    ) {
        Slog.w(LOG_TAG,
            "Ignoring permission already declared by system package")
        return@forEachIndexed
    }
    // system 包接管非 system 定义,清除所有旧 flags
    newState.externalState.userIds.forEachIndexed { _, userId ->
        newState.externalState.appIdPackageNames
            .forEachIndexed { _, appId, _ ->
                setPermissionFlags(appId, userId, info.name, 0)
            }
    }
}

非 system 重复定义被忽略;system 接管时清除所有 appId/用户的旧状态,因为 protection、group 和签名 owner 已经改变。

3.3 定义变化 ​

kotlin
val groupChanged = info.isRuntime &&
    info.group != old.groupName
val protectionChanged =
    (info.isRuntime && !old.isRuntime) ||
        (info.isInternal && !old.isInternal)
if (groupChanged || protectionChanged) {
    newState.externalState.userIds.forEachIndexed { _, userId ->
        newState.externalState.appIdPackageNames
            .forEachIndexed { _, appId, _ ->
                setPermissionFlags(appId, userId, info.name, 0)
            }
    }
}

同一 owner 改变 runtime group 或 protection level 时,已有 grant 必须清零后重算;否则组自动授予或保护级别变化可能造成权限升级。

4. 动态权限 ​

4.1 写入 API ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
fun MutateStateScope.addPermissionTree(permission: Permission) {
    newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
}

fun MutateStateScope.addPermission(
    permission: Permission, isSynchronousWrite: Boolean = false,
) {
    val mode = if (isSynchronousWrite) WriteMode.SYNCHRONOUS else WriteMode.ASYNCHRONOUS
    newState.mutateSystemState(mode).mutatePermissions()[permission.name] = permission
}

动态 API 也进入 AccessState 的 definition map。write mode 只影响落盘时机,不改变 tree owner 或 protection。

4.2 动态 owner ​

kotlin
private fun MutateStateScope.updatePermissionIfDynamic(
    permission: Permission
): Permission {
    if (!permission.isDynamic) return permission
    val tree = findPermissionTree(permission.name) ?: return permission
    return permission.copy(
        permissionInfo = PermissionInfo(permission.permissionInfo)
            .apply { packageName = tree.packageName },
        appId = tree.appId,
        isReconciled = true,
    )
}

动态 permission 的最终 owner 来自 tree。tree 重新出现时,definition 会复制 tree 的包名和 appId。

5. 声明与评估 ​

5.1 requested list ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
val packageNames = newState.externalState.appIdPackageNames[appId]!!
val requesting = MutableIndexedList<PackageState>()
packageNames.forEachIndexed { _, name ->
    val state = newState.externalState.packageStates[name]!!
    val pkg = state.androidPackage
    if (pkg != null && permissionName in pkg.requestedPermissions) {
        requesting += state
    }
}

shared UID 下,任一包的 uses-permission 都成为 appId 评估输入。没有声明的包不会因 definition 存在而自动获得权限。

5.2 未定义权限 ​

kotlin
val permission = newState.systemState.permissions[permissionName]
if (permission == null) {
    if (oldFlags == 0) {
        setPermissionFlags(appId, userId, permissionName,
            PermissionFlags.INSTALL_REVOKED)
    }
    return
}

请求先于定义出现时写入 INSTALL_REVOKED,防止未来同名 normal permission 出现后悄悄授予既有包。

6. 删除与裁剪 ​

6.1 trim 定义 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
private fun MutateStateScope.trimPermissions(
    packageName: String,
    changed: MutableIndexedSet<String>,
) {
    val packageState = newState.externalState.packageStates[packageName]
    val pkg = packageState?.androidPackage
    if (packageState != null && pkg == null) return
    newState.systemState.permissions.forEachReversedIndexed {
        index, name, permission ->
        val updated = updatePermissionIfDynamic(permission)
        newState.mutateSystemState().mutatePermissions().putAt(index, updated)
        if (updated.packageName == packageName &&
            (packageState == null || pkg!!.permissions.noneIndexed {
                _, parsed -> !parsed.isTree && parsed.name == name
            })
        ) {
            newState.mutateSystemState().mutatePermissions().removeAt(index)
            changed += name
        }
    }
}

包卸载或更新后不再声明的 permission 会被 trim,并触发所有受影响包重评估。动态 permission 若仍被 tree 所有,则先更新 owner。

6.2 包移除 ​

kotlin
override fun MutateStateScope.onPackageRemoved(
    packageName: String, appId: Int
) {
    check(packageName !in newState.externalState.disabledSystemPackageStates)
    val changed = MutableIndexedSet<String>()
    trimPermissions(packageName, changed)
    if (appId in newState.externalState.appIdPackageNames) {
        trimPermissionStates(appId)
        evaluateAllPermissionStatesForAppId(appId)
    }
    changed.forEachIndexed { _, name ->
        evaluatePermissionStateForAllPackages(name, null)
    }
}

移除包先裁剪定义,再按 appId 重新评估。shared UID 仍有其他包时,不会简单清空整个 appId 的权限状态。

7. 持久化与冲突 ​

权限定义进入 AccessState.systemState.permissions/permissionTrees/permissionGroups;每用户 grant flags 进入 AccessState.userStates[userId].appIdPermissionFlags。AccessCheckingService.mutateState 在策略变更后调用 AccessPersistence.write(newState),因此定义和状态更新在同一 copy-on-write 事务中提交。

遇到权限声明但查不到时,按顺序检查解析后的 AndroidPackage.permissions、tree owner、同名 owner 冲突、trim 是否移除了 definition,最后才检查 appId/user flags。

8. 验证方法 ​

8.1 重复定义 ​

安装两个普通包声明同名 permission,预期后者被忽略;将后者置为 system 包时,它可以接管非 system owner,并清除所有 appId 的旧 flags。instant app 声明 permission-group 应被忽略。

8.2 权限树 ​

创建合法 tree 子权限与相似错误前缀,前者必须归属 tree owner,后者不能匹配。卸载 tree owner 后,动态权限应在 trim/owner 更新路径中消失或转移。

8.3 定义变化 ​

更新同一 permission 的 group 或 protection,已有用户 flags 应被清零并重新评估。

8.4 未定义请求 ​

让包请求当前不存在的权限,确认 appId/user flags 先记录 INSTALL_REVOKED;定义包出现后仍需重新评估。

bash
adb shell pm list permissions -f -g
adb shell dumpsys package <package.name>
adb shell pm check-permission <permission> <package.name> --user 0

9. 源码路线 ​

  1. ParsedPackage 的 permission/group/tree 字段:确认 Manifest 解析输出。
  2. AppIdPermissionPolicy.onPackageAdded:跟踪 adoption、add、trim 和重评估。
  3. addPermissionGroups:阅读 instant app 与 owner 冲突。
  4. addPermissions:阅读 tree owner、重复 owner 和定义变化。
  5. findPermissionTree/updatePermissionIfDynamic:理解动态命名空间。
  6. evaluatePermissionStateForAppId:观察 uses-permission 如何进入 appId/user 状态。
  7. trimPermissions/onPackageRemoved:跟踪定义和 shared UID 状态裁剪。

权限定义与声明的主线是“Manifest 事实进入系统状态,再由策略评估请求者”:tree 决定命名空间 owner,system/non-system 规则决定冲突,trim 维护定义生命周期,uses-permission 才成为授予输入。