Android 5.1 AppOps总结

Posted lyf5231

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 5.1 AppOps总结相关的知识,希望对你有一定的参考价值。

什么是AppOps

android App在AndroidManifest.xml中申请了很多运行时需要获取的权限,例如

<uses-permission android:name="android.permission.NFC" />

用户在使用某个新安装的App的时候,经常会有弹窗弹出是否允许App获取某个权限,当确认获取后,用户才能正常使用该功能。而AppOps是framework提供给用户,确切点说是开发人员,更多操作应用权限的一个途径。用户可以对某个App的权限根据自己的需求进行禁止或者放行等。
在我理解,android系统中涉及App应用权限的主要涉及3个部分:

1. 系统默认的应用权限; 
2. AppOps Policy文件;
3. 通过调用AppOpsManager中的接口对某一权限进行设置;

开发人员可以利用Policy文件和AppOpsManager中的接口对默认的应用权限进行修改。后续文章将会按照上面三部分进行展开。

涉及的类

以AppOps名字开始的类包括

Settings上层

packages/apps/Settings/src/com/android/settings/applications/AppOpsCategory.java
packages/apps/Settings/src/com/android/settings/applications/AppOpsDetails.java
packages/apps/Settings/src/com/android/settings/applications/AppOpsState.java
packages/apps/Settings/src/com/android/settings/applications/AppOpsSummary.java

frameworks/base/cmds/appops下面有个appops的应用程序

frameworks/native/libs/binder/AppOpsManager.cpp
frameworks/native/include/binder/AppOpsManager.h

Appops核心实现类,AppOpsService是功能具体实现,AppOpsManager是Service相关的Manager,是SDK中的一部分。AppOpsPolicy大概意思是,系统出厂时预制的系统App(System-app)和用户app(User-app)的一些默认的权限。非常有用,我们可以把我们一些预制的信任apk的权限都设置为允许,而不用老是弹窗提示。

frameworks/base/services/core/java/com/android/server/AppOpsService.java
frameworks/base/services/core/java/com/android/server/AppOpsPolicy.java
frameworks/base/core/java/android/app/AppOpsManager.java 

常用名词

在framework中,
将某一权限称为Op,即operation,操作的意思。在AppOpsManager类中用以OP_开头的int表示具体权限,例如OP_COARSE_LOCATION,既表示coarse gps权限;
将某一权限的所对应的动作称为mode。在AppOpsManager类中用以MODE_开头的int表示动作,例如MODE_ALLOWED,表示允许相关权限的执行,即获取了相应的权限。

权限管理是如何触发的

当我们第一次在使用App的时候,访问某些系统权限时,例如访问wifi,都会弹出一个对话框询问用户是否允许访问wifi等。下面是WifiManager.java中打开WiFi的代码,

    public boolean setWifiEnabled(boolean enabled) 
        if (mAppOps.noteOp(AppOpsManager.OP_WIFI_CHANGE) !=
                AppOpsManager.MODE_ALLOWED)
            return false;
        try 
            return mService.setWifiEnabled(enabled);
         catch (RemoteException e) 
            return false;
        
    

在打开wifi前,首先会去调用AppOpsManager中的noteOp函数,如果返回值不是允许,即MODE_ALLOWED,直接返回false,不允许打开wifi。从MODE_ALLOWED的注释我们就能看出,系统一般都是通过checkOp、noteOp、startOp去检测权限,但是使用的地方不同。关于noteOp,见后文。

/**
 * Result from @link #checkOp, @link #noteOp, @link #startOp: the given caller is
 * allowed to perform the given operation.
 */
public static final int MODE_ALLOWED = 0;

系统默认应用权限

android系统中包含了strict模式和普通模式。什么是strict模式?
在AppOpsService类中有个变量,顾名思义,判断系统是否开启strict模式。

final boolean mStrictEnable;

mStrictEnable = AppOpsManager.isStrictEnable();
    public static boolean isStrictEnable() 
        return SystemProperties.getBoolean("persist.sys.strict_op_enable", false);
    

可以看出,通过设置persist.sys.strict_op_enable为true,即开启了strict模式。那么strict模式和普通模式有什么差异?我们下面接着上一段(权限管理是如何触发的)分析。
调用–>

    public int noteOp(int op) 
        return noteOp(op, Process.myUid(), mContext.getOpPackageName());
    

调用–>

    /**
     * Make note of an application performing an operation.  Note that you must pass
     * in both the uid and name of the application to be checked; this function will verify
     * that these two match, and if not, return @link #MODE_IGNORED.  If this call
     * succeeds, the last execution time of the operation for this app will be updated to
     * the current time.
     */
    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) 
        
        return MODE_IGNORED;
    

从上面的注释我们也能证实我们前面的描述,noteOp主要是在一个app执行某个operation时需要note,即检验是否能够执行该操作,拥有该权限。函数中实际的实现还是调用了AppOps的实现类AppOpsService的noteOperation方法。
调用–>

  @Override
    public int noteOperation(int code, int uid, String packageName) 
            //我们这里只看getOpLocked函数,其他省略
            Op op = getOpLocked(ops, code, true);
            if (isOpRestricted(uid, code, packageName)) 
                return AppOpsManager.MODE_IGNORED;
            
    

调用–>

    private Op getOpLocked(Ops ops, int code, boolean edit) 
        int mode;
        //如果该app是第一次使用,肯定返回空
        Op op = ops.get(code);
        if (op == null) 
            if (!edit) 
                return null;
            
            mode = getDefaultMode(code, ops.uid, ops.packageName);
            op = new Op(ops.uid, ops.packageName, code, mode);
            ops.put(code, op);
        
        if (edit) 
            scheduleWriteLocked();
        
        return op;
    

调用–>

   private int getDefaultMode(int code, int uid, String packageName) 
        int mode = AppOpsManager.opToDefaultMode(code,
                isStrict(code, uid, packageName));
        //首先检查某个op是否在strict模式下,mPolicy就是上面提到的policy文件
        //如果op在strict模式下,同时mPolicy存在,则从mPolicy中取默认权限
        if (AppOpsManager.isStrictOp(code) && mPolicy != null) 
            int policyMode = mPolicy.getDefualtMode(code, packageName);
            if (policyMode != AppOpsManager.MODE_ERRORED) 
                mode = policyMode;
            
        
        return mode;
    
   public static int opToDefaultMode(int op, boolean isStrict) 
        //是strict模式,返回sOpDefaultStrictMode数组
        if (isStrict)
            return sOpDefaultStrictMode[op];
        //普通模式,返回sOpDefaultMode数组
        return sOpDefaultMode[op];
    
    //mStrictEnable是enable的,同时是app,则返回true
    private boolean isStrict(int code, int uid, String packageName) 
        if (!mStrictEnable)
            return false;

        return UserHandle.isApp(uid);
    

从上面的分析,我们可以看出在strict模式下,op的对应默认mode在sOpDefaultStrictMode数组中查找,而普通模式下mode在sOpDefaultMode找。例如对于OP_COARSE_LOCATION,在strict模式下默认是询问AppOpsManager.MODE_ASK,而在普通模式下默认是AppOpsManager.MODE_ALLOWED。

AppOps Policy 文件

由上文可知,在获取某个op的默认权限时,如果在strict模式下,同时mPolicy不为null的时候,会去从mPolicy中读取权限。下面分析AppOps Policy 文件,
在AppOpsService中,有个默认的policy文件/system/etc/appops_policy.xml,

static final String DEFAULT_POLICY_FILE = "/system/etc/appops_policy.xml";

同时,在AppOpsService中有个AppOpsPolicy的instance,

AppOpsPolicy mPolicy;
    private void readPolicy() 
        if (mStrictEnable) 
            mPolicy = new AppOpsPolicy(new File(DEFAULT_POLICY_FILE), mContext);
            mPolicy.readPolicy();
            mPolicy.debugPoilcy();
         else 
            mPolicy = null;
        
    

在strict模式下,实例化了一个AppOpsPolicy类,一个参数为默认的policy文件

   public AppOpsPolicy(File file, Context context) 
        super();
        mFile = file;
        mContext = context;
    

调用–>

   void readPolicy() 
        FileInputStream stream;
        synchronized (mFile) 
            try 
                stream = new FileInputStream(mFile);
             catch (FileNotFoundException e) 
                Slog.i(TAG, "App ops policy file (" + mFile.getPath()
                        + ") not found; Skipping.");
                return;
                    String tagName = parser.getName();
                    if (tagName.equals("user-app")
                            || tagName.equals("system-app")) 
                        readDefaultPolicy(parser, tagName);
                     else if (tagName.equals("application")) 
                        readApplicationPolicy(parser);
                     else 
                        Slog.w(TAG, "Unknown element under <appops-policy>: "
                                + parser.getName());
                        XmlUtils.skipCurrentTag(parser);
                    

    

上面的函数就是读Policy文件的函数,,其实主要包括2个函数readDefaultPolicy(parser, tagName)和readApplicationPolicy(parser),我们也可以从函数中推测出policy文件的格式,起码包含user-app、system-app、application三个tag,user-app和system-app这两个tag对应的函数是readDefaultPolicy(parser, tagName),application tag对应的是readApplicationPolicy(parser),即

<appops-policy version="1">
    <user-app permission="ask" show="true"/>
    <system-app permission="allowed" show="false"/>

    <application>
    <!-- Example:

        <pkg name="com.android.dialer" type="system-app">
            <op name="android:call_phone" permission="ask" show="true"/>
        </pkg>

    -->
        <pkg name="com.android.calendar" type="system-app">
            <op name="android:read_contacts" permission="ask" show="true"/>
        </pkg>
        <pkg name="com.android.email" type="system-app">
            <op name="android:read_contacts" permission="ask" show="true"/>
        </pkg>
   </application>
</appops-policy>

其中permission可以有allowed,ignored,ask和其他值,如下代码

   /** @hide */
    public static int stringToMode(String permission) 
        if ("allowed".equalsIgnoreCase(permission)) 
            return AppOpsManager.MODE_ALLOWED;
         else if ("ignored".equalsIgnoreCase(permission)) 
            return AppOpsManager.MODE_IGNORED;
         else if ("ask".equalsIgnoreCase(permission)) 
            return AppOpsManager.MODE_ASK;
        
        return AppOpsManager.MODE_ERRORED;
    

分别对应AppOpsManager.MODE_ALLOWED,AppOpsManager.MODE_IGNORED,AppOpsManager.MODE_ASK,其他字符串都会返回AppOpsManager.MODE_ERRORED。
而show表示是否将该权限show给用户,让用户去修改权限,如下所示,true和false分别对应CONTROL_SHOW,CONTROL_NOSHOW,如果为其他值,则对应CONTROL_UNKNOWN。

public static int stringToControl(String show) 
    if ("true".equalsIgnoreCase(show)) 
        return CONTROL_SHOW;
     else if ("false".equalsIgnoreCase(show)) 
        return CONTROL_NOSHOW;
    
    return CONTROL_UNKNOWN;

下面我们分析下推测出的AppOps Policy文件,首先在application标签外,有两个user-app,system-app的标签,分别的意思是:默认user-app(/data/app)的权限是,permission=”ask”,ask即AppOpsManager.MODE_ASK,意思是会有弹出框提示用户去点击是否允许该权限。show=”true”,允许用户去修改该权限,默认user-app的权限是true,允许修改(可以参考设置—安全—应用操作,点开某个app后,即可以修改权限是允许、提示、还是禁止)。同理,system-app(/system/app),permission=”allowed”,默认权限是允许,show=”false”,不允许用户操作。
以上两个是系统和用户app的默认权限,在application标签内,用户可以自定义添加自己app规则(如果没有自定义app规则,默认就是执行上面两个默认规则,如果有当前app自定义规则,则执行该规则,好理解吧),以搜狗输入法为例:

    <pkg name="com.sohu.inputmethod.sogou" type="user-app" permission="allowed" show="true">
    </pkg>

pkg标签包围, name=”com.sohu.inputmethod.sogou”,name是app的包名,type为user-app或者system-app,permission=”allowed”,所有申请的权限默认允许,show=”true”,同时指出用户去修改,show就是展示出来的意思。
通过添加上面的规则,你会发现,搜狗输入法再也不弹出位置这些权限的窗口让你来选择了。
在 AppOpsPolicy中有个成员,用来保存Policy文件所描述的这些规则,HashMap保存的格式为(packageName,PolicyPkg),每个App是其中的一项。

HashMap<String, PolicyPkg> mPolicy = new HashMap<String, PolicyPkg>();

其中PolicyPkg为AppOpsPolicy中的内部类,

//该内部类就是用来保存上面的 <op name="android:read_contacts" permission="ask" show="true"/>
public final static class PolicyOp 
        public int op;
        public int mode;
        public int show;

        public PolicyOp(int op, int mode, int show) 
            this.op = op;
            this.mode = mode;
            this.show = show;
        

        @Override
        public String toString() 
            return "PolicyOp [op=" + op + ", mode=" + mode + ", show=" + show
                    + "]";
        
    

public final static class PolicyPkg extends SparseArray<PolicyOp> 
        public String packageName;
        public int mode;
        public int show;
        public String type;

        public PolicyPkg(String packageName, int mode, int show, String type) 
            this.packageName = packageName;
            this.mode = mode;
            this.show = show;
            this.type = type;
        

        @Override
        public String toString() 
            return "PolicyPkg [packageName=" + packageName + ", mode=" + mode
                    + ", show=" + show + ", type=" + type + "]";
        

    

下面先介绍读取user-app和system-app这两个tag的函数,读取完成后,在mPolicy 中保存的格式为(“user-app”,PolicyPkg )或者(“system-app”,PolicyPkg ),

private void readDefaultPolicy(XmlPullParser parser, String packageName)
        throws NumberFormatException, XmlPullParserException, IOException 
     //必须是user-app或者system-app
    if (!"user-app".equalsIgnoreCase(packageName)
            && !"system-app".equalsIgnoreCase(packageName)) 
        return;
    
    int mode = AppOpsManager.stringToMode(parser.getAttributeValue(null,
            "permission"));
    int show = stringToControl(parser.getAttributeValue(null, "show"));
    if (mode == AppOpsManager.MODE_ERRORED && show == CONTROL_UNKNOWN) 
        return;
    
    /*如果mPolicy没default policy,则新建一个PolicyPkg,一个hash键值对,这个键值对的格式是("user-app",PolicyPkg )或者("system-app",PolicyPkg ),如果已经存在,则修改mode和show*/
    PolicyPkg pkg = this.mPolicy.get(packageName);
    if (pkg == null) 
        pkg = new PolicyPkg(packageName, mode, show, packageName);
        this.mPolicy.put(packageName, pkg);
     else 
        Slog.w(TAG, "Duplicate policy found for package: " + packageName
                + " of type: " + packageName);
        pkg.mode = mode;
        pkg.show = show;
    

接着是读取application tag下的元素,

 private void readApplicationPolicy(XmlPullParser parser)
            throws NumberFormatException, XmlPullParserException, IOException 
            ......
            /*读取下面的pkg tag*/
            String tagName = parser.getName();
            if (tagName.equals("pkg")) 
                readPkgPolicy(parser);
             else 
                Slog.w(TAG,
                        "Unknown element under <application>: "
                                + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            
        

调用–>
读取pkg本身的元素,如果pkg tag下还有op tag,继续读op tag。

 private void readPkgPolicy(XmlPullParser parser)
            throws NumberFormatException, XmlPullParserException, IOException 
        String packageName = parser.getAttributeValue(null, "name");
        if (packageName == null)
            return;
        String appType = parser.getAttributeValue(null, "type");
        if (appType == null)
            return;
        int mode = AppOpsManager.stringToMode(parser.getAttributeValue(null,
                "permission"));
        int show = stringToControl(parser.getAttributeValue(null, "show"));
        String key = packageName + "." + appType;
        PolicyPkg pkg = this.mPolicy.get(key);
        /*可以看到这里的键值对的键是以packageName + "." + appType形式,例如sogou输入法的键就为com.sohu.inputmethod.sogou.user-app,和default policy相同,如果mPolicy中没有该元素,则新建,否则修改mode和show。*/
        if (pkg == null) 
            pkg = new PolicyPkg(packageName, mode, show, appType);
            this.mPolicy.put(key, pkg);
         else 
            Slog.w(TAG, "Duplicate policy found for package: " + packageName
                    + " of type: " + appType);
            pkg.mode = mode;
            pkg.show = show;
        

        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")) 
                readOpPolicy(parser, pkg);
             else 
                Slog.w(TAG, "Unknown element under <pkg>: " + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            
        
    

从上可以看出pkg下面的键值对形式为(“com.sohu.inputmethod.sogou.user-app”,PolicyPkg),下面接着读pkg下面的op tag。
调用–>
由于PolicyPkg extends SparseArray,所以PolicyPkg 内部有个保存pkg下相关op的一个SparseArray,保存的格式为(code,PolicyOp),code是个int,是稀疏数组的键吧。

private void readOpPolicy(XmlPullParser parser, PolicyPkg pkg)
            throws NumberFormatException, XmlPullParserException, IOException 
        if (pkg == null) 
            return;
        
        String opName = parser.getAttributeValue(null, "name");
        if (opName == null) 
            Slog.w(TAG, "Op name is null");
            return;
        
        int code = AppOpsManager.stringOpToOp(opName);
        if (code == AppOpsManager.OP_NONE) 
            Slog.w(TAG, "Unknown Op: " + opName);
            return;
        
        int mode = AppOpsManager.stringToMode(parser.getAttributeValue(null,
                "permission"));
        int show = stringToControl(parser.getAttributeValue(null, "show"));
        if (mode == AppOpsManager.MODE_ERRORED && show == CONTROL_UNKNOWN) 
            return;
        
        PolicyOp op = pkg.get(code);
        if (op == null) 
            op = new PolicyOp(code, mode, show);
            pkg.put(code, op);
         else 
            Slog.w(TAG, "Duplicate policy found for package: "
                    + pkg.packageName + " type: " + pkg.type + " op: " + op.op);
            op.mode = mode;
            op.show = show;
        
    

至此,AppOps Policy文件已经读完。

AppOpsService准备工作

在AppOpsService类中,首先有个稀疏数组的成员mUidOps,顾名思义,涉及到Uid和Ops相关的东西。在android中,uid被赋予linux不同的任务,不同的App享有不同的Uid。作为稀疏数组的键,而对应的值为HashMap

final SparseArray<HashMap<String, Ops>> mUidOps
        = new SparseArray<HashMap<String, Ops>>();

Op是一个内部类,保存了一个op(权限)的详细信息,例如从什么时候开始运行的,等等。

public final static class Op 
        public final int uid;
        public final String packageName;
        public final int op;
        public int mode;
        public int duration;
        public long time;
        public long rejectTime;
        public int nesting;
        public int noteOpCount;
        public int startOpCount;
        public PermissionDialogReqQueue dialogReqQueue;
        final ArrayList<IBinder> clientTokens;

        public Op(int _uid, String _packageName, int _op, int _mode) 
            uid = _uid;
            packageName = _packageName;
            op = _op;
            mode = _mode;
            dialogReqQueue = new PermissionDialogReqQueue();
            clientTokens = new ArrayList<IBinder>();
        
    

而Ops也是一个内部类,包含了一个SparseArray,op的稀疏数组。Ops既是Op的复数,很多个Op的意思。

 public final static class Ops extends SparseArray<Op> 
        public final String packageName;
        public final int uid;
        public final boolean isPrivileged;

        public Ops(String _packageName, int _uid, boolean _isPrivileged) 
            packageName = _packageName;
            uid = _uid;
            isPrivileged = _isPrivileged;
        
    

所以,mUidOps保存的数据类似(uid,HashMap

详细分析权限管理的触发

前面讲过,在打开wifi时会调用AppOpsManager中的noteOp函数,进行权限的检测,下面详细分析,

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) 
    
    return MODE_IGNORED;

调用–>
调用AppOpsService中的noteOperation,

public int noteOperation(int code, int uid, String packageName) 
        final PermissionDialogReq req;
        verifyIncomingUid(uid);
        verifyIncomingOp(code);

        synchronized (this) 
        //如果mUidOps中没有该App信息,则建立,否则返回Ops
            Ops ops = getOpsLocked(uid, packageName, true);
            if (ops == null) 
                if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                        + " package " + packageName);
                return AppOpsManager.MODE_ERRORED;
            
            //查找或者建立一个Op,放到ops中
            //此外在getOpLocked,参数edit为true的情况下回去写/data/system/appops.xml文件,如果这个
            //op是新建立的,则会马上更新appops.xml文件
            Op op = getOpLocked(ops, code, true);
            //至此,如果该app的这个权限是第一次在函数中处理,这时候已经在mUidOps和appops.xml中存在了
            if (isOpRestricted(uid, code, packageName)) 
                return AppOpsManager.MODE_IGNORED;
            
            if (op.duration == -1) 
                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
                        + " code " + code + " time=" + op.time + " duration=" + op.duration);
            
            op.duration = 0;
            /*将code转换为switchcode*/
            final int switchCode = AppOpsManager.opToSwitch(code);
            /*如果op和switchop不同*/
            final Op switchOp = switchCode != code ? getOpLocked(ops,
                    switchCode, true) : op;
            /*如果mode不是allow和ask,则是被拒绝了,将reject的时间保存*/
            if (switchOp.mode != AppOpsManager.MODE_ALLOWED
                    && switchOp.mode != AppOpsManager.MODE_ASK) 
                if (DEBUG)
                    Log.d(TAG, "noteOperation: reject #" + op.mode
                            + " for code " + switchCode + " (" + code
                            + ") uid " + uid + " package " + packageName);
                op.rejectTime = System.currentTimeMillis();
                return switchOp.mode;
             else if (switchOp.mode == AppOpsManager.MODE_ALLOWED) 
                if (DEBUG)
                    Log.d(TAG, "noteOperation: allowing code " + code + " uid "
                            + uid + " package " + packageName);
                /*如果mode是allow,记录note的时间,并将reject设置为0*/
                op.time = System.currentTimeMillis();
                op.rejectTime = 0;
                return AppOpsManager.MODE_ALLOWED;
             else 
                if (Looper.myLooper() == mLooper) 
                    Log.e(TAG,
                            "noteOperation: This method will deadlock if called from the main thread. (Code: "
                                    + code
                                    + " uid: "
                                    + uid
                                    + " package: "
                                    + packageName + ")");
                    return switchOp.mode;
                
                /*mode是ask,就是跳出来弹窗提醒,这里将op.noteOpCount增加*/
                op.noteOpCount++;
                req = askOperationLocked(code, uid, packageName, switchOp);
            
        
        return req.get();
     

先看getOpsLocked函数

   private Ops getOpsLocked(int uid, String packageName, boolean edit) 
    /*uid为0,包名为root,uid为shell,包名为com.android.shell,uid为system,同时包名为空,则包名为android*/
        if (uid == 0) 
            packageName = "root";
         else if (uid == Process.SHELL_UID) 
            packageName = "com.android.shell";
         else if (uid == Process.SYSTEM_UID) 
            if (packageName == null)
                packageName = "android";
        
        return getOpsRawLocked(uid, packageName, edit);
    

接着调用,

/*有个参数edit,应该表示是不是可以修改mUidOps;
 返回一个Ops,如果mUidOps中还没有该app相关信息,则新建一个HashMap<String, Ops>,放到mUidOps
    */
private Ops getOpsRawLocked(int uid, String packageName, boolean edit) 
        HashMap<String, Ops> pkgOps = mUidOps.get(uid);
        /*mUidOps里面没有这个uid的app*/
        if (pkgOps == null) 
        /*如果edit为false,不允许修改mUidOps,直接就返回了*/
            if (!edit) 
                return null;
            
            /*如果没有这个uid的app,则新建一个,这时pkgOps还是空的*/
            pkgOps = new HashMap<String, Ops>();
            mUidOps.put(uid, pkgOps);
        
        Ops ops = pkgOps.get(packageName);
        if (ops == null) 
            if (!edit) 
                return null;
            
            boolean isPrivileged = false;
            // This is the first time we have seen this package name under this uid,
            // so let's make sure it is valid.
            //由上面的注释,主要是为了检验合法性
            if (uid != 0) 
                final long ident = Binder.clearCallingIdentity();
                try 
                    int pkgUid = -1;
                    try 
                        ApplicationInfo appInfo = ActivityThread.getPackageManager()
                                .getApplicationInfo(packageName, 0, UserHandle.getUserId(uid));
                        if (appInfo != null) 
                            pkgUid = appInfo.uid;
                            isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
                         else 
                            if ("media".equals(packageName)) 
                                pkgUid = Process.MEDIA_UID;
                                isPrivileged = false;
                            
                        
                     catch (RemoteException e) 
                        Slog.w(TAG, "Could not contact PackageManager", e);
                    
                    if (pkgUid != uid) 
                        // Oops!  The package name is not valid for the uid they are calling
                        // under.  Abort.
                        Slog.w(TAG, "Bad call: specified package " + packageName
                                + " under uid " + uid + " but it is really " + pkgUid);
                        return null;
                    
                 finally 
                    Binder.restoreCallingIdentity(ident);
                
            
            //这里新建一个Ops,然后放到pkgOps中
            ops = new Ops(packageName, uid, isPrivileged);
            pkgOps.put(packageName, ops);
        
        return ops;
    

通过上面两步,如果mUidOps中还没有该app相关信息,则新建一个HashMap

private Op getOpLocked(Ops ops, int code, boolean edit) 
    int mode;
    Op op = ops.get(code);
    //如果这个App是第一次进入这个函数,那么op肯定是空的
    if (op == null) 
        if (!edit) 
            return null;
        
        //获取默认的Mode,然后put到ops中
        mode = getDefaultMode(code, ops.uid, ops.packageName);
        op = new Op(ops.uid, ops.packageName, code, mode);
        ops.put(code, op);
    
    //如果允许edit,则执行scheduleWriteLocked
    if (edit) 
        scheduleWriteLocked();
    
    return op;

调用–>getDefaultMode
获取默认Mode前面已经讲过,这里如果这个op是strict的,同时mPolicy不为空,即Policy文件存在,则调用 mPolicy.getDefualtMode(code, packageName);

private int getDefaultMode(int code, int uid, String packageName) 
        int mode = AppOpsManager.opToDefaultMode(code,
                isStrict(code, uid, packageName));
        if (AppOpsManager.isStrictOp(code) && mPolicy != null) 
            int policyMode = mPolicy.getDefualtMode(code, packageName);
            //如果返回值是MODE_ERRORED,那么会返回上面的opToDefaultMode的Mode
            //感觉这种情况唯有mPolicy文件存在,但是没啥东西的时候,getDefualtMode返回MODE_ERRORED
            if (policyMode != AppOpsManager.MODE_ERRORED) 
                mode = policyMode;
            
        
        return mode;
    

注释很明确,就是先从默认规则读相关的Mode,如果还有pkg,那么继续读pkg下面的能匹配的mode,如果有匹配的则会覆盖前面mode的值。mode的默认值是AppOpsManager.MODE_ERRORED。

public int getDefualtMode(int code, String packageName) 
        int mode = AppOpsManager.MODE_ERRORED;
        PolicyPkg pkg;
        String key;
        String type;

        if (mPolicy == null) 
            return mode;
        
        if (DEBUG)
            Slog.d(TAG, "Default mode requested for op=" + code + " package="
                    + packageName);
        type = getAppType(packageName);
        if (type != null) 
            // Get value based on 'type'
            key = type;
            pkg = mPolicy.get(key);
            if (pkg != null && pkg.mode != AppOpsManager.MODE_ERRORED) 
                if (DEBUG)
                    Slog.d(TAG, "Setting value based on type: " + pkg);
                mode = pkg.mode;
            
        
        // Get value based on 'pkg'.
        key 以上是关于Android 5.1 AppOps总结的主要内容,如果未能解决你的问题,请参考以下文章

Win10+miniconda+cuda+cudnn+pytorch1.5.1安装记录(踩坑记录)

Android studio1.5.1 NDK配置开发

有啥办法可以在 Mac 上的 android studio 3.5.1 中修复这个错误

5.1-5.25总结

通过Http接口及SolrNet 两种方法基于Solr5.5.1 实现CURD

每周总结5.1-5.7