1. 程式人生 > >Android 5.1 AppOps總結

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 = packageName;
        if (type != null) {
            key = key + "." + type;
        }
        pkg = mPolicy.get(key);
        if (pkg != null) {
            if (pkg.mode != AppOpsManager.MODE_ERRORED) {
                if (DEBUG)
                    Slog.d(TAG, "Setting value based on packageName: " + pkg);
                mode = pkg.mode;
            }
            // Get value base on 'op'
            PolicyOp op = pkg.get(code);
            if (op != null) {
                if (op.mode != AppOpsManager.MODE_ERRORED) {
                    if (DEBUG)
                        Slog.d(TAG, "Setting value based on op: " + op);
                    mode = op.mode;
                }
            }
        }
        if (DEBUG)
            Slog.d(TAG, "Returning mode=" + mode);
        return mode;
    }

在上面函式getOpLocked中最後會呼叫scheduleWriteLocked,會呼叫寫執行緒去更新一個許可權檔案,

private void scheduleWriteLocked() {
        if (!mWriteScheduled) {
            mWriteScheduled = true;
            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
        }
    }

final Runnable mWriteRunner = new Runnable() {
    public void run() {
        synchronized (AppOpsService.this) {
            mWriteScheduled = false;
            mFastWriteScheduled = false;
            AsyncTask<Void, Vo