AOSP7 多用户源码解析(九)权限系统与多用户的交互机制
系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 Intent 路由的用户隔离](#第八篇:广播分发与 Intent 路由的用户隔离) | 第九篇:权限系统与多用户的交互机制 | [第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南)
Android 6.0 引入运行时权限之后,权限系统出现了一个新的维度:同一应用的权限授予,在不同用户下可以不同 。主用户允许了相机权限,第二个用户可以拒绝;用户 A 的授权对话框结果,不能泄漏给用户 B。此外,跨用户调用(INTERACT_ACROSS_USERS)本身也是一组权限,它们的检查又依赖权限系统,形成一圈闭环。
本篇回答三个问题:
- 权限授予状态在内存里如何按用户存储?为什么安装权限共享、运行时权限隔离?
- 一次
checkPermission调用从ContextImpl到PermissionsState的完整链路是什么? - 运行时权限按用户持久化到哪个文件?设备策略(DPM)的按用户数据又是如何组织的?
需要特别说明:权限系统在 AOSP 7 中有两套完全不同的存储结构------安装权限全局共享 (挂在 USER_ALL 桶上),运行时权限按 userId 隔离 。原稿中"位数组 1 << userId"式的描述并不存在于 AOSP 7 源码中,以下全部基于真实源码。
一、PermissionsState:按用户组织的权限状态
权限状态的载体是每个包(或共享用户)持有一个 PermissionsState 对象,挂载在 PackageSetting 上。
源码路径 :frameworks/base/services/core/java/com/android/server/pm/PermissionsState.java
java
public final class PermissionsState {
/** 权限名 -> 该权限在所有用户上的授予状态 */
private ArrayMap<String, PermissionData> mPermissions;
/** 全局 gids(与用户无关) */
private int[] mGlobalGids = NO_GIDS;
/** 哪些用户还需要权限审查(FLAG_PERMISSION_REVIEW_REQUIRED) */
private SparseBooleanArray mPermissionReviewRequired;
/**
* 安装权限授予给所有设备用户(userId = USER_ALL)
*/
public int grantInstallPermission(BasePermission permission) {
return grantPermission(permission, UserHandle.USER_ALL);
}
/**
* 运行时权限只授予给指定用户
*/
public int grantRuntimePermission(BasePermission permission, int userId) {
enforceValidUserId(userId);
if (userId == UserHandle.USER_ALL) {
return PERMISSION_OPERATION_FAILURE;
}
return grantPermission(permission, userId);
}
/**
* 查询某用户对某权限是否已授予(不区分安装/运行时)
*/
public boolean hasPermission(String name, int userId) {
enforceValidUserId(userId);
if (mPermissions == null) {
return false;
}
PermissionData permissionData = mPermissions.get(name);
return permissionData != null && permissionData.isGranted(userId);
}
}
关键设计 :
mPermissions是两级结构------外层ArrayMap<String, PermissionData>按权限名索引,内层PermissionData按 userId 索引。安装权限和运行时权限共用同一套结构,区别只在于"用户键":安装权限写入UserHandle.USER_ALL(-2)这个特殊桶,运行时权限写入具体 userId。
1.1 PermissionData:单权限的按用户状态
java
// 源码路径同上(PermissionsState.java,内部类)
private static final class PermissionData {
private final BasePermission mPerm;
/**
* 每个用户的授予状态。
* 安装权限只会有一个条目:key 为 UserHandle.USER_ALL。
* 运行时权限每个用户一个条目:key 为具体 userId。
*/
private SparseArray<PermissionState> mUserStates = new SparseArray<>();
public boolean isGranted(int userId) {
if (isInstallPermission()) {
userId = UserHandle.USER_ALL;
}
PermissionState userState = mUserStates.get(userId);
if (userState == null) {
return false;
}
return userState.mGranted;
}
public boolean grant(int userId) {
if (!isCompatibleUserId(userId)) {
return false;
}
if (isGranted(userId)) {
return false;
}
PermissionState userState = mUserStates.get(userId);
if (userState == null) {
userState = new PermissionState(mPerm.name);
mUserStates.put(userId, userState);
}
userState.mGranted = true;
return true;
}
private boolean isInstallPermission() {
return mUserStates.size() == 1
&& mUserStates.get(UserHandle.USER_ALL) != null;
}
}
关键设计 :
isInstallPermission()通过"唯一条目且键为USER_ALL"来识别安装权限。这意味着一个权限要么是全局共享的安装权限,要么是若干用户的运行时权限,二者不会混在一起。grant()中的isCompatibleUserId保证不会往安装权限桶里写具体用户(反之亦然)。
PermissionState 本身非常轻量:
java
public static final class PermissionState {
private final String mName;
private boolean mGranted;
private int mFlags;
public boolean isDefault() {
return !mGranted && mFlags == 0;
}
public boolean isGranted() {
return mGranted;
}
public int getFlags() {
return mFlags;
}
}
关键设计 :
mGranted表示是否授予,mFlags携带FLAG_PERMISSION_USER_SET、FLAG_PERMISSION_USER_FIXED、FLAG_PERMISSION_REVIEW_REQUIRED等标志位。isDefault()为 true 时该条目会被从mUserStates中删除,避免空桶堆积。
1.2 gids 按用户计算
权限还关联 Linux gids(多个权限共享同一 gid 组)。PermissionsState 按用户独立计算:
java
// 源码路径同上(PermissionsState.java)
public int[] computeGids(int userId) {
enforceValidUserId(userId);
int[] gids = mGlobalGids;
if (mPermissions != null) {
final int permissionCount = mPermissions.size();
for (int i = 0; i < permissionCount; i++) {
String permission = mPermissions.keyAt(i);
if (!hasPermission(permission, userId)) {
continue;
}
PermissionData permissionData = mPermissions.valueAt(i);
final int[] permGids = permissionData.computeGids(userId);
if (permGids != NO_GIDS) {
gids = appendInts(gids, permGids);
}
}
}
return gids;
}
关键设计:gids 不是独立存储的,而是从"该用户实际被授予的权限"即时推导。用户 A 授予了某个带 gid 的权限而用户 B 没有,两者的进程 gid 集合就会不同------这是权限隔离落到 Linux 层的方式之一。
二、运行时权限的授予与撤销
应用请求运行时权限,最终走到 PackageManagerService 的两个方法。注意 AOSP 7 中没有 grantRuntimePermissionLPw 这样的内部方法名,真正的入口就是带权限校验的公开方法。
源码路径 :frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
java
@Override
public void grantRuntimePermission(String packageName, String name, final int userId) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"grantRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"grantRuntimePermission");
final int uid;
final SettingBase sb;
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final PermissionsState permissionsState = sb.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(name, userId);
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
throw new SecurityException("Cannot grant system fixed permission "
+ name + " for package " + packageName);
}
// ...development 权限走 install 桶,targetSdk < M 的 legacy 应用直接拒绝...
final int result = permissionsState.grantRuntimePermission(bp, userId);
switch (result) {
case PermissionsState.PERMISSION_OPERATION_FAILURE: {
return;
}
case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
mHandler.post(new Runnable() {
@Override
public void run() {
killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
}
});
}
break;
}
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// 非关键路径:应用丢失后需要重新申请
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
}
关键设计 :权限校验有三层------
GRANT_RUNTIME_PERMISSIONS(signature|installer|verifier)、enforceCrossUserPermission(跨用户需INTERACT_ACROSS_USERS_FULL)、FLAG_PERMISSION_SYSTEM_FIXED(策略固定的权限不可授予)。注意第三层校验用的name和userId都是显式参数:系统策略可以把某个权限对特定用户 固定为拒绝,此时即使用户在界面上点了"允许"也会被SecurityException拦截。
gids 变化时通过 killUid(appId, userId, ...) 杀掉该用户下对应 appId 的进程------隔离再次体现在进程维度:用户 0 的授权变更不会触发用户 10 的进程重启。
revokeRuntimePermission 结构对称:
java
// 源码路径同上(PackageManagerService.java)
@Override
public void revokeRuntimePermission(String packageName, String name, int userId) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
"revokeRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"revokeRuntimePermission");
synchronized (mPackages) {
// ...同样的 pkg/bp/sb 查找与 FLAG_PERMISSION_SYSTEM_FIXED 校验...
// 撤销后若 gids 变化同样 killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED)
}
}
关键设计 :撤销与授予共用
enforceCrossUserPermission(requireFullPermission = true)。这意味着INTERACT_ACROSS_USERS(非 FULL)只能操作自己用户下的权限,无法跨用户改别人的授权状态。
三、checkPermission 的完整链路
3.1 客户端入口
源码路径 :frameworks/base/core/java/android/app/ContextImpl.java
java
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
try {
return ActivityManagerNative.getDefault().checkPermission(
permission, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
关键设计 :客户端不解析 userId------
checkPermission(permission, pid, uid)只有三个参数,用户维度完全由服务端从 uid 推导。这保证了客户端无法通过伪造参数绕过检查。
3.2 AMS 中转
源码路径 :frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
java
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, uid, -1, true);
}
/**
* 这里只处理自己 pid(system_server)的快捷路径,
* 其余委托给 ActivityManager 的静态方法
*/
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
关键设计 :AMS 自身不做权限判定,只处理
system_server(pid == MY_PID)的放行,其余全部委托给ActivityManager.checkComponentPermission静态方法。
3.3 ActivityManager 静态判定
源码路径 :frameworks/base/core/java/android/app/ActivityManager.java
java
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root、system_server 放行
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated 进程没有任何权限
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// 同一个 app(owningUid 与 uid 同 app)放行
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// 非 exported 直接拒绝
if (!exported) {
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
关键设计 :判定顺序是"系统身份 → isolated 进程 → 同 app → exported → 查权限表"。
UserHandle.isSameApp比较的是 appId(uid / 100000无关,appId = uid % 100000),即跨用户但同 appId 的进程视为同一个应用------这是共享 UID(sharedUserId)跨用户场景的基础。
3.4 PMS 终端:从 uid 推导 userId
源码路径 :frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
java
@Override
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
final SettingBase ps = (SettingBase) obj;
final PermissionsState permissionsState = ps.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
// 特例:ACCESS_FINE_LOCATION 隐含 ACCESS_COARSE_LOCATION
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName)
&& permissionsState.hasPermission(
Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
// ...同样有 FINE 隐含 COARSE 的特例...
}
}
}
return PackageManager.PERMISSION_DENIED;
}
关键设计 :
UserHandle.getUserId(uid)是整条链路中唯一一次"用户推导"------userId = uid / 100000,appId = uid % 100000。之后所有判定都按这个 userId 查PermissionsState。mSystemPermissions是给平台组件(如 shell)用的旁路表,与包无关。
注意 hasPermission 内部会区分安装权限和运行时权限(见第一节):安装权限查 USER_ALL 桶,运行时权限查具体 userId 桶。两个桶都查不到就是拒绝。
3.5 带包名的 checkPermission
PMS 还有一个不走 uid 而直接按包名 + userId 查询的重载,供系统内部(如 ContentProvider 权限判定)使用:
java
// 源码路径同上(PackageManagerService.java)
@Override
public int checkPermission(String permName, String pkgName, int userId) {
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
final PackageParser.Package p = mPackages.get(pkgName);
if (p != null && p.mExtras != null) {
final PackageSetting ps = (PackageSetting) p.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
// 同样的 FINE 隐含 COARSE 特例
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName)
&& permissionsState.hasPermission(
Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
return PackageManager.PERMISSION_DENIED;
}
关键设计 :这个重载不做跨用户权限检查 ,因为调用方(系统进程内部)已经持有正确的上下文;外部 Binder 调用一律走
checkUidPermission那条链路,由 AMS 中转。
四、跨用户权限的定义与校验
4.1 权限定义
源码路径 :frameworks/base/core/res/AndroidManifest.xml
xml
<permission android:name="android.permission.INTERACT_ACROSS_USERS"
android:protectionLevel="signature|privileged|development" />
<!-- @SystemApi Fuller form of INTERACT_ACROSS_USERS
that removes restrictions on where broadcasts can be sent -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
android:protectionLevel="signature|installer" />
<permission android:name="android.permission.MANAGE_USERS"
android:protectionLevel="signature|privileged" />
<!-- @hide 允许创建、删除用户并获取用户列表 -->
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
<permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"
android:protectionLevel="signature|installer|verifier" />
关键设计 :
INTERACT_ACROSS_USERS是signature|privileged|development,INTERACT_ACROSS_USERS_FULL是signature|installer,MANAGE_USERS是signature|privileged。GRANT_RUNTIME_PERMISSIONS的持有者是权限授予应用(Permission Controller)和包安装验证器(verifier)。这些权限本身的授予状态同样落在PermissionsState里,受同一套机制管理。
4.2 enforceCrossUserPermission
第七篇已经分析过 PMS 的这个私有方法,此处补充它在权限链路中的位置------它被 grantRuntimePermission、revokeRuntimePermission、getApplicationEnabledSetting 等所有"带 userId 参数"的 PMS API 调用:
java
// 源码路径同上(PackageManagerService.java)
void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
boolean checkShell, String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
if (checkShell) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
if (userId == UserHandle.getUserId(callingUid)) return;
if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
if (requireFullPermission) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} else {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, message);
}
}
}
关键设计 :判定逻辑是"同用户放行 → 系统 uid 放行 → 否则要求跨用户权限"。
requireFullPermission决定要 FULL 还是普通版本。权限检查本身是递归安全的:enforceCallingOrSelfPermission走ContextImpl.checkPermission→ AMS →checkUidPermission链路,查的是调用者 uid 对应的权限状态,不存在循环。
五、运行时权限的按用户持久化
5.1 文件位置与写入入口
运行时权限不写 packages.xml,而是每个用户单独一个文件。
源码路径 :frameworks/base/services/core/java/com/android/server/pm/Settings.java
java
private File getUserRuntimePermissionsFile(int userId) {
// 而非 Environment.getUserSystemDirectory(userId),为测试方便
File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}
public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
if (sync) {
mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
} else {
mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
}
}
关键设计 :文件路径为
/data/system/users/<userId>/runtime-permissions.xml(RUNTIME_PERMISSIONS_FILE_NAME常量值)。按用户分文件意味着删除用户 10 时只需删掉users/10/整个目录,权限数据随之消失,无需清理packages.xml。
5.2 写入逻辑
java
// 源码路径同上(Settings.java,内部类 RuntimePermissionPersistence)
private void writePermissionsSync(int userId) {
AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));
ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
synchronized (mLock) {
mWriteScheduled.delete(userId);
final int packageCount = mPackages.size();
for (int i = 0; i < packageCount; i++) {
String packageName = mPackages.keyAt(i);
PackageSetting packageSetting = mPackages.valueAt(i);
if (packageSetting.sharedUser == null) {
PermissionsState permissionsState = packageSetting.getPermissionsState();
List<PermissionState> permissionsStates = permissionsState
.getRuntimePermissionStates(userId);
if (!permissionsStates.isEmpty()) {
permissionsForPackage.put(packageName, permissionsStates);
}
}
}
// sharedUser 同理,写入 TAG_SHARED_USER 块
}
FileOutputStream out = null;
try {
out = destination.startWrite();
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startDocument(null, true);
serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
String fingerprint = mFingerprints.get(userId);
if (fingerprint != null) {
serializer.attribute(null, ATTR_FINGERPRINT, fingerprint);
}
final int packageCount = permissionsForPackage.size();
for (int i = 0; i < packageCount; i++) {
String packageName = permissionsForPackage.keyAt(i);
List<PermissionState> permissionStates = permissionsForPackage.valueAt(i);
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, packageName);
writePermissions(serializer, permissionStates);
serializer.endTag(null, TAG_PACKAGE);
}
// sharedUser 块同理
// restored 权限块(TAG_RESTORED_RUNTIME_PERMISSIONS)同理
serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
serializer.endDocument();
destination.finishWrite(out);
}
}
关键设计 :写入的是
getRuntimePermissionStates(userId)的运行时权限 列表,安装权限不写这个文件(它们写在packages.xml的包定义里,全局共享)。fingerprint属性记录当前构建指纹,用于 OTA 升级后判断"默认权限是否已授予"(mDefaultPermissionsGranted)。
5.3 写入调度
java
// 源码路径同上(Settings.java)
public void writePermissionsForUserAsyncLPr(int userId) {
final long currentTimeMillis = SystemClock.uptimeMillis();
if (mWriteScheduled.get(userId)) {
mHandler.removeMessages(userId);
// 距上次未写变更若超过 2 秒,立即落盘
final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
.get(userId);
final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
- lastNotWrittenMutationTimeMillis;
if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
mHandler.obtainMessage(userId).sendToTarget();
return;
}
final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis
+ MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0);
final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS, maxDelayMillis);
Message message = mHandler.obtainMessage(userId);
mHandler.sendMessageDelayed(message, writeDelayMillis);
} else {
mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
Message message = mHandler.obtainMessage(userId);
mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
mWriteScheduled.put(userId, true);
}
}
关键设计 :防抖写入------首次变更延迟 200ms,连续变更最长 2s 内必须落盘。调度以 userId 为键 (
SparseBooleanArray mWriteScheduled、SparseLongArray mLastNotWrittenMutationTimesMillis),各用户独立计时,互不阻塞。
六、设备策略(DPM)的按用户数据
6.1 数据结构
源码路径 :frameworks/base/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
java
final class DevicePolicyData {
final int mUserHandle;
// 密码策略、锁屏、用户配置完成状态等(按用户一份)
int mPasswordOwner = -1;
long mLastMaximumTimeToLock = -1;
boolean mUserSetupComplete = false;
int mUserProvisioningState;
int mPermissionPolicy;
// 该用户下的所有激活的 admin
final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
// 各策略字段(证书、lock task 包、代理排除列表等)...
}
final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
关键设计 :
mUserData是SparseArray<DevicePolicyData>,以 userId 为键,每个用户一份完整的策略数据。注意 AOSP 7 中没有DeviceAdminData这个类名------admin 状态类叫ActiveAdmin,且 admin 是挂到DevicePolicyData.mAdminMap(ArrayMap<ComponentName, ActiveAdmin>)里,不是单独的 per-user 字典。
6.2 ActiveAdmin:单 admin 的策略字段
java
// 源码路径同上(DevicePolicyManagerService.java)
static class ActiveAdmin {
final DeviceAdminInfo info;
int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
int minimumPasswordLength = DEF_MINIMUM_PASSWORD_LENGTH;
int minimumPasswordUpperCase = DEF_MINIMUM_PASSWORD_UPPER_CASE;
int minimumPasswordLowerCase = DEF_MINIMUM_PASSWORD_LOWER_CASE;
int minimumPasswordLetters = DEF_MINIMUM_PASSWORD_LETTERS;
int minimumPasswordNumeric = DEF_MINIMUM_PASSWORD_NUMERIC;
int minimumPasswordSymbols = DEF_MINIMUM_PASSWORD_SYMBOLS;
int minimumPasswordNonLetter = DEF_MINIMUM_PASSWORD_NON_LETTER;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
long passwordExpirationTimeout = DEF_PASSWORD_EXPIRATION_TIMEOUT;
long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE;
int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED;
boolean encryptionRequested = false;
boolean disableCamera = false;
boolean disableCallerId = false;
boolean disableScreenCapture = false; // 只能由 device/profile owner 设置
boolean requireAutoTime = false; // 只能由 device owner 设置
boolean forceEphemeralUsers = false; // 只能由 device owner 设置
ActiveAdmin parentAdmin;
final boolean isParent;
}
关键设计 :
ActiveAdmin直接持有策略字段(密码质量、禁用相机、禁用截屏等),isParent标记是否为"父 admin"(Work Profile 场景下 profile owner 的 admin 会引用 parent admin)。getProfileOwnerAdminLocked(userId)、getDeviceOwnerAdminLocked()等查询方法都按 userId 定位到对应的DevicePolicyData再取 admin。
6.3 Work Profile 创建的用户侧入口
UserManager 提供的 profile 创建 API(DPM 侧最终调用它):
源码路径 :frameworks/base/core/java/android/os/UserManager.java
java
public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle) {
try {
return mService.createProfileForUser(name, flags, userHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
源码路径 :frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
java
@Override
public UserInfo createProfileForUser(String name, int flags, int userId) {
// ...权限校验(CREATE_USERS / MANAGE_USERS)与参数校验...
// 调用 createUserInternal 走与创建普通用户相同的初始化链路
}
关键设计 :
createProfileForUser(name, flags, userHandle)的第三个参数是父用户的 userId ,不是ComponentName。flags 传入UserInfo.FLAG_MANAGED_PROFILE表示工作配置。DPM 侧(DevicePolicyManager)没有createAndManageUser这样的公开方法------创建 profile 并设为 profile owner 的流程由Provisioning相关代码和setProfileOwner串联完成,具体见第十篇。
七、调试技巧
7.1 查看某包在某用户下的权限状态
bash
# dumpsys package 会输出每个用户的 grantedPermissions
adb shell dumpsys package com.example.app | grep -A 30 "User 0:"
adb shell dumpsys package com.example.app | grep -A 30 "User 10:"
# 对比两个用户的 grantedPermissions 列表,即可看到运行时权限的差异
7.2 按用户授予 / 撤销运行时权限
bash
# 给用户 10 授予相机权限
adb shell pm grant --user 10 com.example.app android.permission.CAMERA
# 撤销
adb shell pm revoke --user 10 com.example.app android.permission.CAMERA
# pm 内部调用 PackageManagerService.grantRuntimePermission / revokeRuntimePermission
7.3 查看权限持久化文件
bash
# 每个用户独立的运行时权限文件
adb shell ls /data/system/users/
adb shell cat /data/system/users/10/runtime-permissions.xml
7.4 权限日志
bash
# PMS 权限操作日志
adb logcat -s PackageManager
# 权限被拒绝时的异常栈
adb logcat | grep -E "SecurityException|PERMISSION_DENIED"
八、本篇总结
- 两级权限存储 :
PermissionsState外层按权限名(ArrayMap<String, PermissionData>)、内层按 userId(SparseArray<PermissionState>)。安装权限占USER_ALL桶全局共享,运行时权限按具体 userId 隔离。 - checkPermission 链路 :
ContextImpl.checkPermission(permission, pid, uid)→AMS.checkPermission→ActivityManager.checkComponentPermission(系统身份/isolated/同 app/exported 判定)→PMS.checkUidPermission(UserHandle.getUserId(uid)推导用户,查PermissionsState)。 - 运行时权限持久化 :每个用户独立文件
/data/system/users/<id>/runtime-permissions.xml,由RuntimePermissionPersistence按 userId 防抖写入;安装权限则写在packages.xml。 - 跨用户权限 :
INTERACT_ACROSS_USERS(signature|privileged|development)/INTERACT_ACROSS_USERS_FULL(signature|installer)/MANAGE_USERS(signature|privileged),统一由enforceCrossUserPermission在 PMS 各带 userId 的 API 中校验。 - DPM 按用户组织 :
mUserData(SparseArray<DevicePolicyData>)每用户一份,内含mAdminMap(ArrayMap<ComponentName, ActiveAdmin>);admin 策略字段直接挂在ActiveAdmin上。
关键源码路径:
frameworks/base/services/core/java/com/android/server/pm/PermissionsState.java
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/services/core/java/com/android/server/pm/Settings.java
frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/core/java/android/app/ActivityManager.java
frameworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/os/UserManager.java
frameworks/base/core/res/AndroidManifest.xml
frameworks/base/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
下一篇 :[第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南) ------ 全系列最后一篇,深入 Work Profile 的完整创建链路、跨 profile 交互(startActivityAsUser + INTERACT_ACROSS_USERS)与实战开发要点。