相关资源:
android grantRuntimePermission 详解
android GrantPermissionsActivity 详解
AppOps 对于Normal permission 的控制
前言:
最近在处理android M 之后权限处理和M 之前权限处理的过程中碰到AppOps 中的setUidMode 和setMode 两个函数,android 源生的应用中用的是setUidMode,但是如果要是用于区分权限管控,我觉得这个不是很适合,这里总结一下,期待大神的不吝赐教!
源码分析:
setUidMode():
@Override
public void setUidMode(int code, int uid, int mode) {
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
verifyIncomingOp(code);
code = AppOpsManager.opToSwitch(code); //找到switch 之后的op code
synchronized (this) {
final int defaultMode = AppOpsManager.opToDefaultMode(code);//找到default mode
UidState uidState = getUidStateLocked(uid, false); //找到UidState
if (uidState == null) {
if (mode == defaultMode) {
return;
}
uidState = new UidState(uid);
uidState.opModes = new SparseIntArray();
uidState.opModes.put(code, mode);
mUidStates.put(uid, uidState);
scheduleWriteLocked();
} else if (uidState.opModes == null) {
if (mode != defaultMode) {
uidState.opModes = new SparseIntArray();
uidState.opModes.put(code, mode);
scheduleWriteLocked();
}
} else {
if (uidState.opModes.get(code) == mode) {
return;
}
if (mode == defaultMode) { //如果恢复到default mode,那么UidState中的opModes delete掉
uidState.opModes.delete(code);
if (uidState.opModes.size() <= 0) {
uidState.opModes = null;
}
} else {
uidState.opModes.put(code, mode);//如果是不同的mode,这里保存一下
}
scheduleWriteLocked(); //普通记忆,一般是30分钟
}
}
...
... //callback 回调
}
这里主要是UidState:
private static final class UidState {
public final int uid;
public ArrayMap<String, Ops> pkgOps;
public SparseIntArray opModes;
public UidState(int uid) {
this.uid = uid;
}
public void clear() {
pkgOps = null;
opModes = null;
}
public boolean isDefault() {
return (pkgOps == null || pkgOps.isEmpty())
&& (opModes == null || opModes.size() <= 0);
}
}
1、存放uid
2、对于同一个uid 不同pkg 对应的Ops 存放在pkgOps中
这个pkgOps 有两个来源:
- 开机的时候读取文件appops.xml(路径在AMS中初始化AppOps 的时候传入)
- 新的app 在访问mode(noteOperation) 的时候
3、op 对应的mode 存放在OpModes 中,这里存放的是非default 的mode
setMode():
public void setMode(int code, int uid, String packageName, int mode) {
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
verifyIncomingOp(code);
ArrayList<Callback> repCbs = null;
code = AppOpsManager.opToSwitch(code); //找到switch 之后的code
synchronized (this) {
UidState uidState = getUidStateLocked(uid, false); //这里好像是多余的
Op op = getOpLocked(code, uid, packageName, true); //找到Op
if (op != null) {
if (op.mode != mode) { //mode 发生变化
op.mode = mode;
ArraySet<Callback> cbs = mOpModeWatchers.get(code);
if (cbs != null) {
if (repCbs == null) {
repCbs = new ArrayList<>();
}
repCbs.addAll(cbs);
}
cbs = mPackageModeWatchers.get(packageName);
if (cbs != null) {
if (repCbs == null) {
repCbs = new ArrayList<>();
}
repCbs.addAll(cbs);
}
if (mode == AppOpsManager.opToDefaultMode(op.op)) { //如果mode 变为default mode
// If going into the default mode, prune this op
// if there is nothing else interesting in it.
pruneOp(op, uid, packageName);
}
scheduleFastWriteLocked(); //快速记忆,需要10s等待
}
}
}
...
... //callback 回调
}
来看下getOpLocked(),同setUidMode中说到,UidState 中的pkgOps也是同样的形式进行添加。
private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
Ops ops = getOpsRawLocked(uid, packageName, edit);
if (ops == null) {
return null;
}
return getOpLocked(ops, code, edit);
}
这个函数功能:
- 创建UidState(如果原先没有uid 对应的UidState)
- 创建Ops
- UidState中的pkgOps 添加
- 创建Op
- 重新存放appops.xml(这个要30分钟后了)
再来看下当mode 为default mode 的时候会调用:
private void pruneOp(Op op, int uid, String packageName) {
if (op.time == 0 && op.rejectTime == 0) {
Ops ops = getOpsRawLocked(uid, packageName, false);
if (ops != null) {
ops.remove(op.op);
if (ops.size() <= 0) {
UidState uidState = ops.uidState;
ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
if (pkgOps != null) {
pkgOps.remove(ops.packageName);
if (pkgOps.isEmpty()) {
uidState.pkgOps = null;
}
if (uidState.isDefault()) {
mUidStates.remove(uid);
}
}
}
}
}
}
这里一般指的是没有经过noteOperation,op.time 和 op.rejectTime 都是0,会将所有ops、pkgOps中相关的部分remove 掉。
checkOperation():
public int checkOperation(int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
synchronized (this) {
if (isOpRestrictedLocked(uid, code, resolvedPackageName)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
if (uidState != null && uidState.opModes != null
&& uidState.opModes.indexOfKey(code) >= 0) {
return uidState.opModes.get(code);
}
Op op = getOpLocked(code, uid, resolvedPackageName, false);
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
}
return op.mode;
}
}
之所以提出setUidMode 和setMode 的区别,主要是checkOperation(),在最开始的时候会先到UidState中确认mode,如果没有才会到Op 中确认。
上面讲到,如果使用setUidMode,一般是对应uid 相同的package 不同的情况,然后是在mode为default 的时候会将mode 从opModes中remove 掉。当mode set 为default 的时候最后肯定会到Op中查找,而这个时候如果是单独的package ,在setUidMode 之后并没有更新Op 就可能出现错误的mode 返回。
所以正常情况下,如果是单独的package 使用的话,直接使用setMode 即可。
还有一点需要注意的:setUidMode() 和setMode() 两函数最开始的条件判断不能修改,需要对AppOps的permission 进行确认,这个CTS 中的AppOpsTest 中明确要求,如果缺少这个条件,CTS 测试会出现failed。
另外,在Android P 中,setMode 和setUidMode 所需要的权限有所变化,coding 的时候注意了:
android.Manifest.permission.MANAGE_APP_OPS_MODES