AppOps 中setUidMode 和setMode区别

相关资源:

Android Runtime Permission 详解

android grantRuntimePermission 详解

android GrantPermissionsActivity 详解

AppOps 对于Normal permission 的控制

Android native 权限控制流程

AppOps 中setUidMode 和setMode区别

android M 之前应用权限和M 之后的应用权限控制

 

前言:

最近在处理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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

私房菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值