Android 8.0權(quán)限管理源碼分析

權(quán)限的目的是保護用戶的隱私。應(yīng)用訪問敏感數(shù)據(jù)芥炭,例如通訊錄和SMS恃慧,還有系統(tǒng)特性,如攝像頭彪薛,都需要申請權(quán)限良瞧;根據(jù)權(quán)限的類型,系統(tǒng)會自動賦予挚冤,或者讓用戶決定是否給予權(quán)限赞庶;

權(quán)限等級可以分為四個等級:
protectionLevel
(1)Normal
權(quán)限被聲明為Normal級別澳骤,任何應(yīng)用都可以申請为肮,在安裝應(yīng)用時肤京,不會直接提示給用戶,點擊全部才會展示棋枕。
(2)Dangerous
權(quán)限被聲明為Dangerous級別妒峦,任何應(yīng)用都可以申請,在安裝應(yīng)用時窥浪,會直接提示給用戶笛丙。
(3)Signature
權(quán)限被聲明為Signature級別,只有和該apk(定義了這個權(quán)限的apk)用相同的私鑰簽名的應(yīng)用才可以申請該權(quán)限符相。
frameworks/base/core/res/AndroidManifest.xml聲明的權(quán)限為Signature級別蠢琳,那么只有Android官方使用相同私鑰簽名的應(yīng)用才可以申請該權(quán)限傲须。
(4)SignatureOrSystem
權(quán)限被聲明為SignatureOrSystem級別泰讽,有兩種應(yīng)用可以申請該權(quán)限昔期。
1)和該apk(定義了這個權(quán)限的apk)用相同的私鑰簽名的應(yīng)用
2)在/system/app目錄下的應(yīng)用

對于APP targetSdkVersion >= 23, 在應(yīng)用安裝的時候不會顯示應(yīng)用需要的權(quán)限; 應(yīng)需要在使用時動態(tài)申請累澡,并且用戶可以選擇拒絕授權(quán)訪問這些權(quán)限般贼,已授予過的權(quán)限奥吩,用戶也可以去APP設(shè)置頁面去關(guān)閉授權(quán)霞赫;
杜宇targetSdkVersion <23 肥矢, 會在安裝時候顯示應(yīng)用需要的所有權(quán)限;如果用戶允許旅东,就會授予應(yīng)用所有權(quán)限楼誓,如果不允許,就會停止安裝主守;
AppOpsManager Target < 23; 由AppOpsService處理榄融,持久化到appops.xml;
Runtime-permission Target >= 23;由PackageManagerService 處理涎才;持久化到runtime-permission.xml

另外權(quán)限不僅是系統(tǒng)特性力九, 還可以用于限制調(diào)用者;如Activtiy 中如果有android:permission棕兼,那么只有這個caller擁有這個權(quán)限抵乓,才可以啟動這個Activity;
這個權(quán)限會在茎芋,startActivity(),startActivityForResult的時候檢測田弥,如果沒有會拋出SecurityException铡原;

普通權(quán)限

image.png

權(quán)限組

系統(tǒng)根據(jù)權(quán)限用途又定義了權(quán)限組煤杀,每個權(quán)限都可屬于一個權(quán)限組沪哺,每個權(quán)限組可以包含多個權(quán)限。例如聯(lián)系人權(quán)限組枯途,包含讀取聯(lián)系人籍滴、修改聯(lián)系人和獲取賬戶三個權(quán)限。

  • 如果應(yīng)用申請訪問一個危險權(quán)限晚岭,而此應(yīng)用目前沒有對應(yīng)的權(quán)限組內(nèi)的任何權(quán)限勋功,系統(tǒng)會彈窗提示用戶要訪問的權(quán)限組(注意不是權(quán)限)。例如無論你申請READ_CONTACTS還是WRITE_CONTACTS片择,都是提示應(yīng)用需要訪問聯(lián)系人信息骚揍。
  • 如果用戶申請訪問一個危險權(quán)限,而應(yīng)用已經(jīng)授權(quán)同權(quán)限組的其他權(quán)限嘲叔,則系統(tǒng)會直接授權(quán)借跪,不會再與用戶有交互酌壕。例如應(yīng)用已經(jīng)請求并授予了READ_CONTACTS權(quán)限卵牍,那么當應(yīng)用申請WRITE_CONTACTS時沦泌,系統(tǒng)會立即授予該權(quán)限。下面為危險權(quán)限和權(quán)限組:
image.png

ADB工具

配合runtime permission, 也新增了一些相關(guān)的adb 命令:

  1. 查看所有的dangerous permissions:
              adb shell pm list permissions –g –d
    
  2. 安裝app并且對所有列在app manifest文件下的所有permission給予授權(quán):
              adb install -g <path_to_apk>
    
  3. 授權(quán)給某個app某個permission:
             adb pm grant <package_name> <permission_name>
    
  4. 撤銷授權(quán):
              adb pm revoke <package_name> <permission_name>
    

Setting APP中對應(yīng)用權(quán)限管理:

菜單:
應(yīng)用和通知:AppAndNotificationDashboardFragment.java释牺;
應(yīng)用信息:ManageApplications.java,列出所有APP猩谊;
具體應(yīng)用詳細界面:InstalledAppDetails.java祭刚;
點擊"權(quán)限",進入


image.png

Packageinstaller APP 權(quán)限修改界面

詳細列出應(yīng)用需要的權(quán)限
ManagePermissionsActivity.java
AppPermissionsFragment.java:
管理應(yīng)用權(quán)限類:
AppPermissions.java:
構(gòu)造函數(shù):


image.png

通過packageInof 獲得應(yīng)用所有權(quán)限暗甥,在封裝成permissiongroup撤防;

image.png

在AppPremissionsFragment中顯示Group


image.png

AppPermissionGroup.java
針對targetSdkVersion是否高于23做了不同處理寄月,targetSdkVersion<23的所有的權(quán)限都在packages.xml中剥懒,grante一直是true合敦,無法被跟新;如果targetSdkVersion>=23支持動態(tài)權(quán)限管理,那就更新動態(tài)權(quán)限保檐,并將其持久化到runtime-permission.xml中崔梗,并更新其granted值,如果targetSdkVersion<23 ,也不是動態(tài)管理扔亥,那就只更新AppOps谈为,這是4.3引入的老的動態(tài)權(quán)限管理模型,不過這里主要是將權(quán)限持久化到appops.xml中粘茄,更新權(quán)限后30分鐘才會持久化到appops.xml中,不過對于其granted的值是沒有做任何更新的儒搭,僅僅是更新了packages.xml中的flag芙贫,這個flag可以配合appops.xml標識是否被授權(quán)(對于targetSdkVersion<23的適用。

禁止權(quán)限

revokeRuntimePermissionsrevoke(false)
----------mPackageManager.revokeRuntimePermission更新權(quán)限
----------mPackageManager.updatePermissionFlag更新permissionflag


image.png

對于targetSdkVersion < 23的應(yīng)用


image.png

允許權(quán)限

grantRuntimePermissions(false)
----------mPackageManager.grantRuntimePermission
1 判斷是否可以動態(tài)權(quán)限管理默穴,targetSdkVersion> = 23
2 更新AppOps服務(wù)
3 調(diào)用packageManager更新其runtime-permission.xml 中g(shù)ranted值
4 更新permissionflag

image.png

image.png

如果不是動態(tài)權(quán)限蓄诽,
1 更新AppOps 服務(wù)
2 更新permissionflag


image.png

image.png

PackageManagerService-- grantRuntimePermission

1 PackageManagerService中通過mPackage得到目標pkg 仑氛;
2 通過mSettings.mPermissions獲取到目標權(quán)限bp 锯岖;
3 通過pkg得到一個PermissionsState對象permissionsState甫何;
4 使用permissionsState對象來對pkg的bp進行賦予權(quán)限的操作;
1 通過ensurePermissionData方法獲取一個PermissionData對象pd捶牢;
2 使用pd來賦予權(quán)限
5 持久化到runtime-permission.xml

    private void grantRuntimePermission(String packageName, String name, final int userId,
            boolean overridePolicy) {
        if (!sUserManager.exists(userId)) {
            Log.e(TAG, "No such user:" + userId);
            return;
        }
        final int callingUid = Binder.getCallingUid();

        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                "grantRuntimePermission");

        enforceCrossUserPermission(callingUid, userId,
                true /* requireFullPermission */, true /* checkShell */,
                "grantRuntimePermission");

        final int uid;
        final PackageSetting ps;

        synchronized (mPackages) {
//1 通過mPackage得到目標pkg
            final PackageParser.Package pkg = mPackages.get(packageName);
            if (pkg == null) {
                throw new IllegalArgumentException("Unknown package: " + packageName);
            }
// 2通過mSettings.mPermissions獲取到目標權(quán)限bp 秋麸;
            final BasePermission bp = mSettings.mPermissions.get(name);
            if (bp == null) {
                throw new IllegalArgumentException("Unknown permission: " + name);
            }
            ps = (PackageSetting) pkg.mExtras;
            if (ps == null
                    || filterAppAccessLPr(ps, callingUid, userId)) {
                throw new IllegalArgumentException("Unknown package: " + packageName);
            }
            //app將該permission 注冊到AndroidManifest中
            //并且該permission 是Runtime 或者是development permission
            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);

            // If a permission review is required for legacy apps we represent
            // their permissions as always granted runtime ones since we need
            // to keep the review required permission flag per user while an
            // install permission's state is shared across all users.
            if (mPermissionReviewRequired
                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
                    && bp.isRuntime()) {
                return;
            }

            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
//3 通過pkg得到一個PermissionsState對象permissionsState灸蟆;

            final PermissionsState permissionsState = ps.getPermissionsState();

            final int flags = permissionsState.getPermissionFlags(name, userId);
            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
                throw new SecurityException("Cannot grant system fixed permission "
                        + name + " for package " + packageName);
            }
            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
                throw new SecurityException("Cannot grant policy fixed permission "
                        + name + " for package " + packageName);
            }

            if (bp.isDevelopment()) {
                // Development permissions must be handled specially, since they are not
                // normal runtime permissions.  For now they apply to all users.
                if (permissionsState.grantInstallPermission(bp) !=
                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
                    scheduleWriteSettingsLocked();
                }
                return;
            }

            if (ps.getInstantApp(userId) && !bp.isInstant()) {
                throw new SecurityException("Cannot grant non-ephemeral permission"
                        + name + " for package " + packageName);
            }

            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
                return;
            }
//4 使用permissionsState對象來對pkg的bp進行賦予權(quán)限的操作炒考;

            final int result = permissionsState.grantRuntimePermission(bp, userId);
            switch (result) {
                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
                    return;
                }

                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
                        }
                    });
                }
                break;
            }

            if (bp.isRuntime()) {
                logPermissionGranted(mContext, name, packageName);
            }

            mOnPermissionChangeListeners.onPermissionsChanged(uid);
//5 持久化到runtime-permission.xml

            // Not critical if that is lost - app has to request again.
            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
        }

        // Only need to do this if user is initialized. Otherwise it's a new user
        // and there are no processes running as the user yet and there's no need
        // to make an expensive call to remount processes for the changed permissions.
        if (READ_EXTERNAL_STORAGE.equals(name)
                || WRITE_EXTERNAL_STORAGE.equals(name)) {
            final long token = Binder.clearCallingIdentity();
            try {
                if (sUserManager.isInitialized(userId)) {
                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
                            StorageManagerInternal.class);
                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

權(quán)限檢測 checkUidPermission

1 通過UID 獲得應(yīng)用的SettingBase;
2 通過SettingBase 獲得PermissionsState
3 更具permissionsState 判斷是否具有權(quán)限

    public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(caSllingUid);
        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
        final int userId = UserHandle.getUserId(uid);
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }
        //permName.equals();
        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                if (obj instanceof SharedUserSetting) {
                    if (isCallerInstantApp) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                } else if (obj instanceof PackageSetting) {
                    final PackageSetting ps = (PackageSetting) obj;
                    if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                }
                final SettingBase settingBase = (SettingBase) obj;
                final PermissionsState permissionsState = settingBase.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    if (isUidInstantApp) {
                        BasePermission bp = mSettings.mPermissions.get(permName);
                        if (bp != null && bp.isInstant()) {
                            return PackageManager.PERMISSION_GRANTED;
                        }
                    } else {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

錄音權(quán)限判斷

ServiceUtilities.cpp

1 packagemanager--- checkPermission
2 AppOpsManager--- noteOp

bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
    // we're always OK.
    if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;

    static const String16 sRecordAudio("android.permission.RECORD_AUDIO");

    // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
    // may open a record track on behalf of a client.  Note that pid may be a tid.
    // IMPORTANT: Don't use PermissionCache - a runtime permission and may change.
    const bool ok = checkPermission(sRecordAudio, pid, uid);
    if (!ok) {
        ALOGE("Request requires android.permission.RECORD_AUDIO");
        return false;
    }

    // To permit command-line native tests
    if (uid == AID_ROOT) return true;

    String16 checkedOpPackageName = opPackageName;

    // In some cases the calling code has no access to the package it runs under.
    // For example, code using the wilhelm framework's OpenSL-ES APIs. In this
    // case we will get the packages for the calling UID and pick the first one
    // for attributing the app op. This will work correctly for runtime permissions
    // as for legacy apps we will toggle the app op for all packages in the UID.
    // The caveat is that the operation may be attributed to the wrong package and
    // stats based on app ops may be slightly off.
    if (checkedOpPackageName.size() <= 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder = sm->getService(String16("permission"));
        if (binder == 0) {
            ALOGE("Cannot get permission service");
            return false;
        }

        sp<IPermissionController> permCtrl = interface_cast<IPermissionController>(binder);
        Vector<String16> packages;

        permCtrl->getPackagesForUid(uid, packages);

        if (packages.isEmpty()) {
            ALOGE("No packages for calling UID");
            return false;
        }
        checkedOpPackageName = packages[0];
    }

    AppOpsManager appOps;
    if (appOps.noteOp(AppOpsManager::OP_RECORD_AUDIO, uid, checkedOpPackageName)
            != AppOpsManager::MODE_ALLOWED) {
        ALOGE("Request denied by app op OP_RECORD_AUDIO");
        return false;
    }

    return true;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市测柠,隨后出現(xiàn)的幾起案子缘滥,更是在濱河造成了極大的恐慌朝扼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擎颖,死亡現(xiàn)場離奇詭異搂捧,居然都是意外死亡,警方通過查閱死者的電腦和手機允跑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門搪柑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弱睦,你說我怎么就攤上這事渊额。” “怎么了端圈?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長矗晃。 經(jīng)常有香客問我宴倍,道長,這世上最難降的妖魔是什么鸵贬? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮兆衅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摩疑。我一直安慰自己,他們只是感情好雷袋,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布楷怒。 她就那樣靜靜地躺著,像睡著了一般鸠删。 火紅的嫁衣襯著肌膚如雪倚搬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天捅僵,我揣著相機與錄音,去河邊找鬼庙楚。 笑死趴樱,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的叁征。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼疏虫,長吁一口氣:“原來是場噩夢啊……” “哼啤呼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起官扣,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤惕蹄,失蹤者是張志新(化名)和其女友劉穎治专,沒想到半個月后遭顶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年鸥滨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片老速。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡凸主,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卿吐,到底是詐尸還是另有隱情,我是刑警寧澤箭窜,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布衍腥,位于F島的核電站,受9級特大地震影響婆咸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜块差,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一乖仇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乃沙,春花似錦、人聲如沸警儒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽变姨。三九已至,卻和暖如春定欧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砍鸠。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留录豺,地道東北人饭弓。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像兢哭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迟螺,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容