platform.xml 权限映射
platform.xml 不是一张单一的权限表。Android 17 的 SystemConfig 将系统权限 XML 分成三条链:<permission><group> 描述权限对应的 Linux GID,<assign-permission> 为系统 UID 建立直接权限集合,<split-permission> 为权限策略提供兼容性来源。三类状态的 owner、消费者和生效时机都不同。
1. 三类声明
| 标签 | 内存状态 | 消费者 | 结果 |
|---|---|---|---|
<permission><group> | PermissionEntry.gids/perUser | PermissionService.getGidsForUid | supplementary GID |
<assign-permission> | mSystemPermissions[uid] | checkUidPermission | 系统 UID 直接授权 |
<split-permission> | mSplitPermissions | AccessCheckingService | 隐式来源索引 |
权限有 GID 映射不代表所有声明它的应用都自动获得 GID;getGidsForUid 仍要检查 permission flags。系统 UID 的 assign 也不会授给普通 APK。split 规则只提供重算输入,不直接设置 runtime grant。
2. 配置入口
源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java
switch (tagName) {
case "permission":
readPermission(parser, permFile, allowPermissions);
break;
case "assign-permission":
readAssignPermission(parser, permFile, allowPermissions,
allowVendorAssignPermissions);
break;
case "split-permission":
readSplitPermission(parser, permFile, allowPermissions);
break;
}readPermissionsFromXml 先决定文件所在分区能否声明标签;不允许时记录 logNotAllowedInPartition 并跳过当前标签。解析结果保存在 SystemConfig,后续服务消费内存快照,不会在每次权限检查时重新读文件。
3. 权限与 GID
3.1 PermissionEntry
源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java
public static final class PermissionEntry {
public final String name;
public int[] gids;
public boolean perUser;
PermissionEntry(String name, boolean perUser) {
this.name = name;
this.perUser = perUser;
}
}
final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();没有 <group> 的权限也会生成 entry,只是 gids 为空。它表示“配置存在但没有 Linux 组映射”,不表示权限不存在。
3.2 读取 group
源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java
void readPermission(XmlPullParser parser, String name)
throws IOException, XmlPullParserException {
if (mPermissions.containsKey(name)) {
throw new IllegalStateException(
"Duplicate permission definition for " + name);
}
final boolean perUser = XmlUtils.readBooleanAttribute(
parser, "perUser", false);
final PermissionEntry perm = new PermissionEntry(name, perUser);
mPermissions.put(name, perm);
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
if ("group".equals(parser.getName())) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = Process.getGidForName(gidStr);
if (gid != -1) {
perm.gids = appendInt(perm.gids, gid);
} else {
Slog.w(TAG, "<group> with unknown gid \"" + gidStr);
}
} else {
Slog.w(TAG, "<group> without gid");
}
}
XmlUtils.skipCurrentTag(parser);
}
}重复权限定义直接抛出异常;未知 GID 只记录 warning,不写入数组;perUser 默认为 false。典型配置 <permission name="android.permission.INTERNET"><group gid="inet"/></permission> 只建立映射,不直接授予 INTERNET。
4. GID 计算
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/Permission.kt
inline val hasGids: Boolean
get() = gids.isNotEmpty()
fun getGidsForUser(userId: Int): IntArray =
if (areGidsPerUser) {
IntArray(gids.size) { i -> UserHandle.getUid(userId, gids[i]) }
} else {
gids.copyOf()
}源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
override fun getGidsForUid(uid: Int): IntArray {
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val globalGids = systemConfig.globalGids
service.getState {
val permissionFlags =
with(policy) { getUidPermissionFlags(appId, userId) }
?: return globalGids.copyOf()
val gids = GrowingIntArray.wrap(globalGids)
permissionFlags.forEachIndexed { _, permissionName, flags ->
if (!PermissionFlags.isPermissionGranted(flags)) {
return@forEachIndexed
}
val permission =
with(policy) { getPermissions()[permissionName] }
?: return@forEachIndexed
gids.addAll(permission.getGidsForUser(userId))
}
return gids.toArray()
}
}完整 UID 先拆成 appId/userId。无权限状态时仍返回 global GID;每个权限必须同时满足“已授予、能找到 Permission、Permission 有 GID”才进入结果。perUser 决定 GID 是否按 userId 转换。
5. GID 变化
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
if (permission.hasGids &&
(wasPermissionGranted != isPermissionGranted)
) {
gidsChangedUids += uid
}
gidsChangedUids.forEachIndexed { _, uid ->
if (!handler.hasMessages(uid, 0)) {
handler.obtainMessage(uid, 0).setCallback {
killUid(uid, PermissionManager.KILL_APP_REASON_GIDS_CHANGED)
}.sendToTarget()
}
}
gidsChangedUids.clear()只有具有关联 GID 且 granted 真值发生变化时,UID 才进入集合。状态 mutation 完成后异步 kill,确保进程不会继续使用旧 supplementary groups;消息去重避免一次批量变更重复 kill。
6. UID 权限
6.1 解析 UID 权限
源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java
private void readAssignPermission(XmlPullParser parser, File permFile,
boolean isVendor) throws IOException, XmlPullParserException {
if (AconfigFlags.getInstance().skipCurrentElement(null, parser, true)) {
XmlUtils.skipCurrentTag(parser);
return;
}
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, "<assign-permission> without name in " + permFile);
XmlUtils.skipCurrentTag(parser);
return;
}
if (isVendor && !VENDOR_ASSIGNABLE_PERMISSIONS.contains(perm)) {
Slog.w(TAG, "<assign-permission> for vendor unavailable permission " + perm);
XmlUtils.skipCurrentTag(parser);
return;
}
String uidStr = parser.getAttributeValue(null, "uid");
if (uidStr == null) {
XmlUtils.skipCurrentTag(parser);
return;
}
int uid = Process.getUidForName(uidStr);
if (uid < 0) {
XmlUtils.skipCurrentTag(parser);
return;
}
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms == null) {
perms = new ArraySet<>();
mSystemPermissions.put(uid, perms);
}
perms.add(perm.intern());
XmlUtils.skipCurrentTag(parser);
}vendor 文件有 VENDOR_ASSIGNABLE_PERMISSIONS 限制;缺少属性、未知 UID、Aconfig 禁用条目都不会写入表。同一 UID 的多条声明合并到 ArraySet。
6.2 UID 直接命中
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
private fun isSystemUidPermissionGranted(
uid: Int, permissionName: String
): Boolean {
val uidPermissions = systemConfig.systemPermissions[uid]
?: return false
if (permissionName in uidPermissions) return true
val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
return fullerPermissionName != null &&
fullerPermissionName in uidPermissions
}assign 表是 checkUidPermission 的直接授权来源。查询 key 是完整 UID,只有精确权限或 FULLER_PERMISSIONS 兼容映射命中才返回 true。
7. split-permission
7.1 解析规则
源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java
private void readSplitPermission(XmlPullParser parser, File permFile)
throws IOException, XmlPullParserException {
String featureFlag = parser.getAttributeValue(
"http://schemas.android.com/apk/res/android", "featureFlag");
if (featureFlag == null) {
featureFlag = parser.getAttributeValue(null, "featureFlag");
}
boolean negated = featureFlag != null && featureFlag.strip().startsWith("!");
String source = parser.getAttributeValue(null, "name");
if (source == null) {
XmlUtils.skipCurrentTag(parser);
return;
}
String targetSdkStr = parser.getAttributeValue(null, "targetSdk");
int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT + 1;
if (!TextUtils.isEmpty(targetSdkStr)) {
try {
targetSdk = Integer.parseInt(targetSdkStr);
} catch (NumberFormatException e) {
XmlUtils.skipCurrentTag(parser);
return;
}
}
final int depth = parser.getDepth();
List<String> newPermissions = new ArrayList<>();
while (XmlUtils.nextElementWithin(parser, depth)) {
if ("new-permission".equals(parser.getName())) {
String newName = parser.getAttributeValue(null, "name");
if (!TextUtils.isEmpty(newName)) newPermissions.add(newName);
} else {
XmlUtils.skipCurrentTag(parser);
}
}
if (!newPermissions.isEmpty()) {
mSplitPermissions.add(new SplitPermissionInfo(
source, newPermissions, targetSdk, featureFlag, negated));
}
}缺少 targetSdk 使用高于当前开发版本的默认值;feature flag 支持取反;没有有效目标权限时不保存规则。
7.2 反向索引
源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
private val SystemConfig.implicitToSourcePermissions:
IndexedMap<String, IndexedListSet<String>>
get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
splitPermissions.forEach { info ->
val source = info.splitPermission
info.newPermissions.forEach { newPermission ->
getOrPut(newPermission) { MutableIndexedListSet() } += source
}
}
} as IndexedMap<String, IndexedListSet<String>>XML 的 source → new 在策略输入中反转为 new → source。这样权限重算处理新权限时,可以直接反查旧权限来源,而不用遍历全部规则。
8. 查询和生效边界
源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java
public ArrayList<SplitPermissionInfo> getSplitPermissions(
boolean includeDisabled) {
if (includeDisabled) return mSplitPermissions;
ArrayList<SplitPermissionInfo> filtered = new ArrayList<>();
for (int i = 0; i < mSplitPermissions.size(); i++) {
SplitPermissionInfo info = mSplitPermissions.get(i);
String flag = info.getFeatureFlag();
if (flag == null || !AconfigFlags.getInstance().skip(
null, flag, info.isFeatureFlagNegated())) {
filtered.add(info);
}
}
return filtered;
}includeDisabled 只影响查询结果,不会从原始列表删除规则。修改 XML 也不会让运行中的进程自动获得新 GID;必须经过配置重新加载、权限状态重算和必要的 UID 重启。
9. 源码阅读路线
SystemConfig顶层标签分发:确认权限、assign、split 的入口与分区限制。readPermission/PermissionEntry:确认 GID 名称到数字数组的转换。Permission.getGidsForUser/PermissionService.getGidsForUid:确认授予状态与 global/per-user GID 合并。PermissionService.gidsChangedUids:理解 GID 变化为何触发 UID kill。readAssignPermission/isSystemUidPermissionGranted:区分系统 UID 直接授权和普通包权限。readSplitPermission/getSplitPermissions/implicitToSourcePermissions:确认 target SDK、feature flag 和反向索引。
platform.xml 的真正作用是把配置分别接入 Linux GID 计算、系统 UID 快速授权和权限策略兼容性推导。只有沿各自消费者继续阅读,才能解释一条声明为什么生效、为什么被跳过,以及为什么权限变化会影响进程生命周期。
