AppOpsManager 基本流程和使用方法

最近刚做完通知权限管理的功能,在这里做一下记录。

单个应用的权限管理需要使用到 AppOpsManager 的接口,接下来通过代码记录下:

AppOpsManager 是对外的管理接口,真正实现功能的是 AppOpsService。

一、AppOpsManager的工作流程
1、 AppOpsService 初始化:
public ActivityManagerService(Context systemContext) {
        mContext = systemContext;
        mFactoryTest = FactoryTest.getMode();
        mSystemThread = ActivityThread.currentActivityThread();

        ....
        // TODO: Move creation of battery stats service outside of activity manager service.
        File dataDir = Environment.getDataDirectory();
        File systemDir = new File(dataDir, "system");
        systemDir.mkdirs();
        ....
        mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
        mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
                new IAppOpsCallback.Stub() {
                    @Override public void opChanged(int op, int uid, String packageName) {
                        if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
                            if (mAppOpsService.checkOperation(op, uid, packageName)
                                    != AppOpsManager.MODE_ALLOWED) {
                                runInBackgroundDisabled(uid);
                            }
                        }
                    }
        });
                

从以上代码可知应用信息的储存在“data/system/appops.xml”文件中,格式为:

<app-ops>
<pkg n="root">
<uid n="0" p="false">
<op n="34" t="1499140707282" pu="0" />
</uid>
</pkg>
<pkg n="com.mediatek.xxx">
<uid n="1000" p="false">
<op n="11" t="1499224813388" pu="0" />
<op n="15" m="0" />
<op n="59" t="1499224797843" pu="0" />
<op n="60" t="1499224797843" pu="0" />
<op n="71" t="1499224813389" pu="0" />
<op n="75" t="1499224813387" pu="0" />
<op n="76" t="1499224813387" pu="0" />
<op n="77" t="1499224813388" pu="0" />
</uid>
</pkg>
<pkg n="com.xxx.usercenter">
<uid n="1000" p="true">
<op n="0" t="1498721353479" pu="0" />
<op n="2" t="1498721359244" d="5787" />
<op n="10" t="1498721356900" pu="0" />
<op n="12" t="1498721353481" pu="0" />
<op n="41" t="1498721359246" d="5793" />
<op n="42" t="1498721359226" d="5769" />
<op n="59" t="1498721350806" pu="0" />
<op n="60" t="1498721350806" pu="0" />
<op n="72" m="0" t="1498721353877" pu="0" />
</uid>
</pkg>

初始化 AppOpsService 同时,解析appops.xml文件中的信息并储存在名为 UidState 的内部类中:

    void readState() {
        synchronized (mFile) {
            synchronized (this) {
                FileInputStream stream;
                try {
                    stream = mFile.openRead();
                } catch (FileNotFoundException e) {
                    ....
                    return;
                }
                boolean success = false;
                mUidStates.clear();
                try {
                    XmlPullParser parser = Xml.newPullParser();
                    parser.setInput(stream, StandardCharsets.UTF_8.name());
                    int type;
                        ....
                        String tagName = parser.getName();
                        if (tagName.equals("pkg")) {
                            //读取每个应用权限信息,并保存至UidState.pkgOps变量
                            readPackage(parser);
                        } else if (tagName.equals("uid")) {
                            //读取每个应用对应的相关权限以及权限状态,并保存至UidState.opModes变量
                            readUidOps(parser);
                        } else {
                            Slog.w(TAG, "Unknown element under <app-ops>: "
                                    + parser.getName());
                            XmlUtils.skipCurrentTag(parser);
                        }
                    }
                    success = true;
                } catch (IllegalStateException e) {
                    ....
                }
            }
        }
    }

    void readPackage(XmlPullParser parser) throws NumberFormatException,
            XmlPullParserException, IOException {
        String pkgName = parser.getAttributeValue(null, "n");
        int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            ....
            String tagName = parser.getName();
            if (tagName.equals("uid")) {
                readUid(parser, pkgName);
            } else {
              ....
            }
        }
    }

    void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
            XmlPullParserException, IOException {
        int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
        String isPrivilegedString = parser.getAttributeValue(null, "p");
        boolean isPrivileged = false;
        if (isPrivilegedString == null) {
            try {
                IPackageManager packageManager = ActivityThread.getPackageManager();
                if (packageManager != null) {
                    ApplicationInfo appInfo = ActivityThread.getPackageManager()
                            .getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid));
                    if (appInfo != null) {
                        isPrivileged = (appInfo.privateFlags
                                & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
                    }
                } else {
                    // Could not load data, don't add to cache so it will be loaded later.
                    return;
                }
            } catch (RemoteException e) {
                Slog.w(TAG, "Could not contact PackageManager", e);
            }
        } else {
            isPrivileged = Boolean.parseBoolean(isPrivilegedString);
        }
        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;
            }

            String tagName = parser.getName();
            if (tagName.equals("op")) {
                Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
                String mode = parser.getAttributeValue(null, "m");
                if (mode != null) {
                    op.mode = Integer.parseInt(mode);
                }
                String time = parser.getAttributeValue(null, "t");
                if (time != null) {
                    op.time = Long.parseLong(time);
                }
                time = parser.getAttributeValue(null, "r");
                if (time != null) {
                    op.rejectTime = Long.parseLong(time);
                }
                String dur = parser.getAttributeValue(null, "d");
                if (dur != null) {
                    op.duration = Integer.parseInt(dur);
                }
                String sum = parser.getAttributeValue(null, "sum");
                if (sum != null) {
                    op.mSum = Integer.parseInt(sum);
                }
                String proxyUid = parser.getAttributeValue(null, "pu");
                if (proxyUid != null) {
                    op.proxyUid = Integer.parseInt(proxyUid);
                }
                String proxyPackageName = parser.getAttributeValue(null, "pp");
                if (proxyPackageName != null) {
                    op.proxyPackageName = proxyPackageName;
                }

                UidState uidState = getUidStateLocked(uid, true);
                if (uidState.pkgOps == null) {
                    uidState.pkgOps = new ArrayMap<>();
                }

                Ops ops = uidState.pkgOps.get(pkgName);
                if (ops == null) {
                    ops = new Ops(pkgName, uidState, isPrivileged);
                    uidState.pkgOps.put(pkgName, ops);
                }
                ops.put(op.op, op);
            } else {
                Slog.w(TAG, "Unknown element under <pkg>: "
                        + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            }
        }
    }


 void readUidOps(XmlPullParser parser) throws NumberFormatException,
            XmlPullParserException, IOException {
        final int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
        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;
            }

            String tagName = parser.getName();
            if (tagName.equals("op")) {
                //code对应AppopsManager中的OP值
                final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
                //mode 对应AppopsManager中权限状态,有0 1 2 3四种状态
                final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
                UidState uidState = getUidStateLocked(uid, true);
                if (uidState.opModes == null) {
                    uidState.opModes = new SparseIntArray();
                }
                uidState.opModes.put(code, mode);
            } else {
                Slog.w(TAG, "Unknown element under <uid-ops>: "
                        + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            }
        }
    }
2、 AppOps 权限状态设置流程:

AppOpsManager:


    /** @hide */
    public void setMode(int code, int uid, String packageName, int mode) {
        try {
            mService.setMode(code, uid, packageName, mode);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

AppOpsService:



    @Override
    public void setMode(int code, int uid, String packageName, int mode) {
        /// M: Log enhancement @{
        String callingApp = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
      
        if (Binder.getCallingPid() != Process.myPid()) {
            mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
                    Binder.getCallingPid(), Binder.getCallingUid(), null);
        }
        //验证code值,不在AppOpsManager._NUM_OP范围内会直接抛异常
        verifyIncomingOp(code);
        ArrayList<Callback> repCbs = null;
        code = AppOpsManager.opToSwitch(code);
        synchronized (this) {
            //通过UID去查找UidState 、Op 对象,如果没有就创建一个新的。
            UidState uidState = getUidStateLocked(uid, false);
            Op op = getOpLocked(code, uid, packageName, true);
            if (op != null) {
                //判断是否需要更新appops.xml
                if (op.mode != mode) {
                    op.mode = mode;
                    ....
                    //将app权限信息写入 appops.xml 文件
                    scheduleFastWriteLocked();
                }
            }
        }
       .....
    }

    private void scheduleFastWriteLocked() {
        if (!mFastWriteScheduled) {
            mWriteScheduled = true;
            mFastWriteScheduled = true;
            mHandler.removeCallbacks(mWriteRunner);
            mHandler.postDelayed(mWriteRunner, 10*1000);
        }
    }

mWriteRunner 中通过异步执行了writeState()的写入操作,将更新后的UidState 对象信息重新写入appops.xml中。

2、 AppOps 权限状态读取流程:

<br />
AppOpsManager 中常用的读取方法有四种:

[checkOp](file:///D:/AndroidStudioSDK/docs/reference/android/app/AppOpsManager.html#checkOp%28java.lang.String,%20int,%20java.lang.String%29)([String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) op, int uid, [String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) packageName)

只读取不做记录,权限不通过会抛异常

[noteOp](file:///D:/AndroidStudioSDK/docs/reference/android/app/AppOpsManager.html#noteOp%28java.lang.String,%20int,%20java.lang.String%29)([String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) op, int uid, [String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) packageName)

和checkOp基本相同,但是在检验后会做记录。

[checkOpNoThrow](file:///D:/AndroidStudioSDK/docs/reference/android/app/AppOpsManager.html#checkOpNoThrow%28java.lang.String,%20int,%20java.lang.String%29)([String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) op, int uid, [String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) packageName)

和checkOp类似,但是权限错误,不会抛出SecurityException,而是返回AppOpsManager.MODE_ERRORED.

[noteOpNoThrow](file:///D:/AndroidStudioSDK/docs/reference/android/app/AppOpsManager.html#noteOpNoThrow%28java.lang.String,%20int,%20java.lang.String%29)([String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) op, int uid, [String](file:///D:/AndroidStudioSDK/docs/reference/java/lang/String.html) packageName)

类似noteOp,但不会抛出SecurityException。

以 noteOp 来记录下读取的流程:

AppOpsManager:

public int noteOp(int op, int uid, String packageName) {
        try {
            int mode = mService.noteOperation(op, uid, packageName);
            //如果权限不通过直接抛异常
            if (mode == MODE_ERRORED) {
                throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
            }
            return mode;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

AppOpsService:

 @Override
    public int noteOperation(int code, int uid, String packageName) {
        verifyIncomingUid(uid);
        verifyIncomingOp(code);
        String resolvedPackageName = resolvePackageName(uid, packageName);
        if (resolvedPackageName == null) {
            return AppOpsManager.MODE_IGNORED;
        }
        return noteOperationUnchecked(code, uid, resolvedPackageName, 0, null);
    }

    private int noteOperationUnchecked(int code, int uid, String packageName,
            int proxyUid, String proxyPackageName) {
        synchronized (this) {
            Ops ops = getOpsRawLocked(uid, packageName, true);
            if (ops == null) {
                ....
                return AppOpsManager.MODE_ERRORED;
            }
            //这里会对op 对象的变量重新设值,最终会打印在appops.xml中
            Op op = getOpLocked(ops, code, true);
            if (isOpRestricted(uid, code, packageName)) {
                return AppOpsManager.MODE_IGNORED;
            }
            if (op.duration == -1) {
                  ....
            }
            op.duration = 0;
            final int switchCode = AppOpsManager.opToSwitch(code);
            UidState uidState = ops.uidState;
            // If there is a non-default per UID policy (we set UID op mode only if
            // non-default) it takes over, otherwise use the per package policy.
            if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
                final int uidMode = uidState.opModes.get(switchCode);
                if (uidMode != AppOpsManager.MODE_ALLOWED) {
                    if (!operationFallBackCheck(uidState, switchCode, uid, packageName)) {
                        op.rejectTime = System.currentTimeMillis();
                        op.mSum ++;
                        return uidMode;
                    }
                }
            } else {
                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
                if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
                    if (!operationFallBackCheck(ops, switchOp.op, uid, packageName)) {

                        op.rejectTime = System.currentTimeMillis();
                        op.mSum ++;
                        return switchOp.mode;
                    }
                }
            }
            op.time = System.currentTimeMillis();
            op.rejectTime = 0;
            op.proxyUid = proxyUid;
            op.proxyPackageName = proxyPackageName;
            return AppOpsManager.MODE_ALLOWED;
        }
    }
二、AppOpsManager的使用方法
1.添加静态变量

1.1 添加op:

    /** @hide */
    public static final int OP_BOOT_COMPLETED = 69;

1.2 修改_NUM_OP(op总数+1):

    /** @hide */
    public static final int _NUM_OP = 70;

1.3 添加OP_STR:

    private static final String OPSTR_BOOT_COMPLETED =
            "android:boot_completed";
2.在op静态数组中添加新增op

初始化时会对每个数组的数量进行check,如果与_NUM_OP 值不一致,就会抛异常,会导致开不了机。

    private static final int[] RUNTIME_PERMISSIONS_OPS = {
        ....
        OP_BOOT_COMPLETED,
    }

    private static int[] sOpToSwitch = new int[] {
        ....
        OP_BOOT_COMPLETED,
    }

    private static int[] sOpToSwitchCta = new int[] {
        ....
        OP_BOOT_COMPLETED,
    }

    private static String[] sOpToString = new String[] {
        ....
        OPSTR_BOOT_COMPLETED,
    };

    private static String[] sOpNames = new String[] {
        ....
        "BOOT_COMPLETED",
    };

    private static String[] sOpPerms = new String[] {
        ....
        Manifest.permission.RECEIVE_BOOT_COMPLETED,
    };

    private static String[] sOpRestrictions = new String[] {
        ....
        null, //BOOT_COMPLETED
    };

    private static boolean[] sOpAllowSystemRestrictionBypass = new boolean[] {
        ....
        true, // BOOT_COMPLETED
    };

    private static int[] sOpDefaultMode = new int[] {
        ....
        AppOpsManager.MODE_IGNORED, // OP_BOOT_COMPLETED     
    };

    private static boolean[] sOpDisableReset = new boolean[] {
        ....
        false,     // OP_BOOT_COMPLETED
    };

以上已经将需要添加的op代码添加完毕,接下来需要在系统模块中使用。

3.在系统模块中使用

3.1 设置权限

mAppOpsService.setMode(AppOpsManager.OP_BOOT_COMPLETED, uid, packageName, AppOpsManager.MODE_ALLOWED);

3.2 判断权限状态

 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
                != AppOpsManager.MODE_ALLOWED) {
        ....
 }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容