Android 權限的一些細節
1 哪些app屬於system app?
為了區分privilege app和system app,這里先說明system app是什么,避免之后的討論概念混亂。
在PackageManagerService中對是否是system app的判斷:
具有ApplicationInfo.FLAG_SYSTEM標記的,被視為System app。
1 private static boolean isSystemApp(PackageParser.Package pkg) { 2 return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 3 } 4 5 private static boolean isSystemApp(PackageSetting ps) { 6 return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; 7 }
有兩類app屬於System app:
1.1 第一類System app: 特定的shared uid的app 屬於system app
例如:shared uid為android.uid.system,android.uid.phone,android.uid.log,android.uid.nfc,android.uid.bluetooth,android.uid.shell。這類app都被賦予了ApplicationInfo.FLAG_SYSTEM標志。
在PackageManagerService的構造方法中,代碼如下:
1 mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, 2 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 3 mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, 4 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 5 mSettings.addSharedUserLPw("android.uid.log", LOG_UID, 6 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 7 mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, 8 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 9 mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, 10 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 11 mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, 12 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
1.2 第二類System app: 特定目錄中的app屬於system app
特定目錄包括:/vendor/overlay,/system/framework,/system/priv-app,/system/app,/vendor/app,/oem/app。這些目錄中的app,被視為system app。
在PackageManagerService的構造方法中,代碼如下:
1 // /vendor/overlay folder 2 File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR); 3 scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM 4 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0); 5 6 // Find base frameworks (resource packages without code). /system/framework folder 7 File frameworkDir = new File(Environment.getRootDirectory(), "framework"); 8 scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM 9 | PackageParser.PARSE_IS_SYSTEM_DIR 10 | PackageParser.PARSE_IS_PRIVILEGED, 11 scanFlags | SCAN_NO_DEX, 0); 12 13 // Collected privileged system packages. /system/priv-app folder 14 final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); 15 scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM 16 | PackageParser.PARSE_IS_SYSTEM_DIR 17 | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0); 18 19 // Collect ordinary system packages. /system/app folder 20 final File systemAppDir = new File(Environment.getRootDirectory(), "app"); 21 scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM 22 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); 23 24 // Collect all vendor packages. 25 File vendorAppDir = new File("/vendor/app"); 26 scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM 27 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); 28 29 // Collect all OEM packages. /oem/app folder 30 final File oemAppDir = new File(Environment.getOemDirectory(), "app"); 31 scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM 32 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
scanDirLI參數中的PackageParser.PARSE_IS_SYSTEM最終會被轉換為Package的ApplicationInfo.FLAG_SYSTEM屬性。這個過程相關的代碼流程(簡略):
1 scanDirLI(PackageParser.PARSE_IS_SYSTEM) 2 3 -> scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null); 4 5 -> scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user); 6 7 -> final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user); 8 9 -> scanPackageDirtyLI()中 10 if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) != 0) { 11 pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; 12 }
2 什么是privileged app(特權app)?
注:privileged app,在本文中稱之為 特權app,主要原因是此類特權app可以使用protectionLevel為signatureOrSystem或者protectionLevel為signature|privileged的權限。
從PackageManagerService的isPrivilegedApp()可以看出特權app是具有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED標志的一類app。
1 private static boolean isPrivilegedApp(PackageParser.Package pkg) { 2 return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; 3 }
特權app首先必須是System app。也就是說 System app分為普通的system app和特權的system app。
1 System app = 普通的system app + 特權app
直觀的(但不准確嚴謹)說,普通的system app就是/system/app目錄中的app,特權的system app就是/system/priv-app目錄中的app。
BTW: priv-app 是privileged app的簡寫。
區分普通system app和特權app的目的是澄清這個概念:"signatureOrSystem"權限中的System不是為普通system app提供的,而是只有特權app能夠使用。
進一步說,android:protectionLevel="signatureOrSystem"中的System和android:protectionLevel="signature|privileged"中的privileged的含義是一樣的。
可以從PermissionInfo.java中的fixProtectionLevel()看出來:
// PermissionInfo.java public static int fixProtectionLevel(int level) { if (level == PROTECTION_SIGNATURE_OR_SYSTEM) { // "signatureOrSystem"權限轉換成了"signature|privileged"權限 level = PROTECTION_SIGNATURE | PROTECTION_FLAG_PRIVILEGED; } return level; }
所以,當我們說起system app時,通常指的是前面提到的特定uid和特定目錄中的app,包含了普通的system app和特權app。
當我們說起有訪問System權限或者privileged權限的app時,通常指特權app。
3 哪些app屬於privileged app(特權app)?
特權app首先是System app,然后要具有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED標志。
有兩類app屬於privileged app(特權app):
參考PackageManagerService的構造方法。
3.1 第一類privileged app: 特定uid的app
shared uid為"android.uid.system","android.uid.phone","android.uid.log","android.uid.nfc","android.uid.bluetooth","android.uid.shell"的app被賦予了privileged的權限。這些app同時也是system app。
代碼如下:
1 // PackageManagerService的構造方法 2 mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, 3 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 4 mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, 5 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 6 mSettings.addSharedUserLPw("android.uid.log", LOG_UID, 7 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 8 mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, 9 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 10 mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, 11 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); 12 mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, 13 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
3.2 第二類privileged app:/system/framework和/system/priv-app目錄下的app
/system/framework和/system/priv-app目錄下的app被賦予了privileged的權限。
其中/system/framework目錄中的apk,只是包含資源,不包含代碼(dex)。
代碼如下:
/
1 / PackageManagerService的構造方法 2 // /system/framework 3 File frameworkDir = new File(Environment.getRootDirectory(), "framework"); 4 scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM 5 | PackageParser.PARSE_IS_SYSTEM_DIR 6 | PackageParser.PARSE_IS_PRIVILEGED, 7 scanFlags | SCAN_NO_DEX, 0); 8 9 // /system/priv-app 10 final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); 11 scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM 12 | PackageParser.PARSE_IS_SYSTEM_DIR 13 | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0); 14 15 PackageParser.PARSE_IS_PRIVILEGED標志最終會轉換為Package的ApplicationInfo.PRIVATE_FLAG_PRIVILEGED標志。大概的代碼流程,如下: 16 17 scanDirLI(PackageParser.PARSE_IS_PRIVILEGED) 18 19 -> scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null); 20 21 -> PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user); 22 23 -> final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user); 24 25 -> scanPackageDirtyLI()中 26 if ((parseFlags & PackageParser.PARSE_IS_PRIVILEGED) != 0) { 27 pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; 28 } 29
4 Android app中的權限是必須先聲明后使用嗎?
在本文中,聲明權限是指在AndroidManifest.xml中使用了<permission>,使用權限是指在AndroidManifest.xml中使用了<uses-permission>。獲得權限(或賦予權限)是指真正的可以通過系統的權限檢查,調用到權限保護的方法。
場景:App A中聲明了權限PermissionA,App B中使用了權限PermissionA。
那么App A必須比App B先安裝,App B才能獲取到權限嗎?
答案是不一定,要看具體情況而定。這個具體情況就是權限的保護級別。
情況一:PermissionA的保護級別是normal或者dangerous
App B先安裝,App A后安裝,此時App B沒有獲取到PermissionA的權限。
即,此種情況下,權限必須先聲明再使用。即使App A和App B是相同的簽名。
情況二:PermissionA的保護級別是signature或者signatureOrSystem
App B先安裝,App A后安裝,如果App A和App B是相同的簽名,那么App B可以獲取到PermissionA的權限。如果App A和App B的簽名不同,則App B獲取不到PermissionA權限。
即,對於相同簽名的app來說,不論安裝先后,只要是聲明了權限,請求該權限的app就會獲得該權限。
這也說明了對於具有相同簽名的系統app來說,安裝過程不會考慮權限依賴的情況。安裝系統app時,按照某個順序(例如名字排序,目錄位置排序等)安裝即可,等所有app安裝完了,所有使用權限的app都會獲得權限。
在這里提供一種方法,可以方便的驗證上面的說法:
4.1 驗證某個app是否獲得了某個權限的方法
可以用下面2個命令來驗證:
adb shell dumpsys package permission <權限名>
adb shell dumpsys package <包名>
其中,adb shell dumpsys package permission <權限名>可以查看某個權限是誰聲明的,誰使用了。
adb shell dumpsys package <包名>可以看到某個app是否獲得了某個權限。
例如,App A中聲明了權限com.package.a.PermissionA,App B中使用了權限com.package.a.PermissionA。
App A的包名為com.package.a,App B的包名為com.package.b。
4.1.1 命令一: adb shell dumpsys package permission <權限名>
查看某個權限是誰聲明的,誰使用了。
通過下面的命令,查看com.package.a.PermissionA權限:
adb shell dumpsys package permission com.package.a.PermissionA
輸出結果為:(注:這里只顯示關鍵信息,省略了其他很多信息)
Permission [com.package.a.PermissionA] (a2930ef): sourcePackage=com.package.a // 權限的聲明者 uid=10226 gids=null type=0 prot=normal //保護級別為normal Packages: Package [com.package.b] (350d95): // 權限的使用者 requested permissions: com.package.a.PermissionA install permissions: com.package.a.PermissionA, granted=true, flags=0x0
其中granted=true表明App B 獲取到了com.package.a.PermissionA權限。
如果App B沒有獲取到com.package.a.PermissionA權限,則輸出結果中只有權限的聲明信息,如下:
Permission [com.package.a.PermissionA] (a2930ef):
sourcePackage=com.package.a
uid=10226 gids=null type=0 prot=normal 保護級別為normal
4.1.2 命令二: adb shell dumpsys package <包名>
查看某個app是否獲得了某個權限。
查看App B(包名為com.package.b)是否獲得了權限com.package.a.PermissionA:
adb shell dumpsys package com.package.b
輸出結果為:(注:省略了很多其他信息)
Packages:
Package [com.package.b] (6611d16):
requested permissions: // 申請了哪些權限
com.package.a.PermissionA
install permissions: // 獲得了哪些權限
com.package.a.PermissionA, granted=true, flags=0x0
上面的輸出結果表明,App B獲取到了com.package.a.PermissionA權限。
如果App B沒有獲取到 com.package.a.PermissionA 權限,那么在 install permissions 中不會出現com.package.a.PermissionA, granted=true, flags=0x0。
5 關於signature權限和signatureOrSystem權限的獲取(或拒絕)
這里要說的是這種場景:App A中聲明了權限com.package.a.PermissionA,App B中使用了權限com.package.a.PermissionA。其中com.package.a.PermissionA的保護級別為signature或者signatureOrSystem。App A先安裝,App B后安裝。App B和App A的簽名可能一樣,也可能不一樣。App B的簽名也可能是系統簽名,這是對於廠商的app來說的。
安裝App B時,PackageManagerService會對App B能否獲得com.package.a.PermissionA權限做檢查。
大概的代碼流程如下:
安裝App B的時候:
1 installPackageLI() 2 private void installPackageLI(InstallArgs args, PackageInstalledInfo res) { 3 final File tmpPackageFile = new File(args.getCodePath());// 被安裝的apk的路徑 4 final PackageParser.Package pkg; 5 try { 6 pkg = pp.parsePackage(tmpPackageFile, parseFlags); 7 } 8 ... 9 if (replace) { 10 ... 11 } else { 12 // run here 13 installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, 14 args.user, installerPackageName, volumeUuid, res); 15 } 16 } 17 installNewPackageLI() 18 private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, String installerPackageName, String volumeUuid, PackageInstalledInfo res) { 19 PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user); 20 21 updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);// run here 22 } 23 updateSettingsLI() 24 private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName, 25 String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res, 26 UserHandle user) { 27 28 updatePermissionsLPw(newPackage.packageName, newPackage, 29 UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0 30 ? UPDATE_PERMISSIONS_ALL : 0)); 31 grantPermissionsLPw() 32 private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace, 33 String packageOfInterest) { 34 35 // 遍歷安裝包pkg中所有請求的權限 36 final int N = pkg.requestedPermissions.size(); 37 for (int i=0; i<N; i++) { 38 final String name = pkg.requestedPermissions.get(i); // 權限的名字 39 final BasePermission bp = mSettings.mPermissions.get(name);// 權限的信息 40 41 final String perm = bp.name; 42 boolean allowedSig = false; 43 int grant = GRANT_DENIED; 44 45 final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; // 權限的保護級別, PROTECTION_MASK_BASE 為0xf,signature級別時,bp.protectionLevel = 2;signatureOrSystem級別時,bp.protectionLevel = 0x12,所以對這兩種級別的權限,level都是2,即,PermissionInfo.PROTECTION_SIGNATURE 46 switch (level) { 47 case PermissionInfo.PROTECTION_NORMAL: { // value is 0 48 // For all apps normal permissions are install time ones. 49 grant = GRANT_INSTALL; 50 } break; 51 52 case PermissionInfo.PROTECTION_DANGEROUS: {// value is 1 53 // 略 54 } break; 55 56 // 被安裝app使用了其他app聲明的signature或者signatureOrSystem權限,上面提到的場景會執行到這里 57 case PermissionInfo.PROTECTION_SIGNATURE: {// value is 2 58 // For all apps signature permissions are install time ones. 59 allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); 60 if (allowedSig) { 61 grant = GRANT_INSTALL; 62 } 63 } break; 64 } 65 grantSignaturePermission()
注:pre23的權限,請參考鏈接:關於pre23權限。
1 private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, 2 BasePermission bp, PermissionsState origPermissions) { 3 boolean allowed; 4 // 這里檢查被安裝的app的簽名(pkg.mSignatures)與聲明權限的app的簽名(bp.packageSetting.signatures.mSignatures)是否一致, 5 // 如果不一致,則再檢查被安裝app的簽名是否與系統簽名(mPlatformPackage.mSignatures)一致。 6 // 如果其中一個是一致的,則賦予被安裝app signature權限。 7 // 注意:只要被安裝的app的簽名是系統簽名,則其可以訪問任意第三方聲明的signature權限。 8 allowed = (compareSignatures( 9 bp.packageSetting.signatures.mSignatures, pkg.mSignatures) 10 == PackageManager.SIGNATURE_MATCH) 11 || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) 12 == PackageManager.SIGNATURE_MATCH); 13 14 // 如果被安裝app的簽名既不是聲明權限的app的簽名,也不是系統簽名,則繼續檢查其他標志位。 15 // 首先檢查聲明的權限是否是privileged權限(也就是signatureOrSystem中的System權限),如果權限是privileged的,那么對於系統應用(滿足isSystemApp(pkg))並且是privileged應用(滿足isPrivilegedApp(pkg)),就會賦予 16 if (!allowed && (bp.protectionLevel 17 & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) { 18 if (isSystemApp(pkg)) {// 如果被安裝的app是system應用(見前面對system app的說明) 19 if (pkg.isUpdatedSystemApp()) { // 如果是更新系統app 20 // 略 21 } else {// 不是更新系統app,如果被安裝的app是privileged的,則賦予其權限。也就是說,對於/system/priv-app目錄中的app,會獲取到權限。這種情況發生在privileged app第一次被安裝時,[或者系統被root后,強行push apk到system/priv-app目錄?(有待驗證)]。 22 allowed = isPrivilegedApp(pkg); 23 } 24 } 25 } 26 27 if (!allowed) { 28 if (!allowed && (bp.protectionLevel 29 & PermissionInfo.PROTECTION_FLAG_PRE23) != 0 30 && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {// 如果之前沒有獲取到權限,再判斷是否是pre23的權限。如果是pre23的權限,且被安裝的app的targetVersion是22及以下,則賦予其權限。 31 allowed = true; 32 } 33 if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0 34 && pkg.packageName.equals(mRequiredInstallerPackage)) {// 只有指定的system installer才能使用該權限。 35 allowed = true; 36 } 37 if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0 38 && pkg.packageName.equals(mRequiredVerifierPackage)) {// 只有指定的system verifier才能使用該權限。 39 allowed = true; 40 } 41 if (!allowed && (bp.protectionLevel 42 & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0 43 && isSystemApp(pkg)) { // 對於preinstalled級別的權限,只有系統app可以使用該權限。 44 // Any pre-installed system app is allowed to get this permission. 45 allowed = true; 46 } 47 if (!allowed && (bp.protectionLevel 48 & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { 49 // For development permissions, a development permission 50 // is granted only if it was already granted. 51 allowed = origPermissions.hasInstallPermission(perm); 52 } 53 } 54 return allowed; 55 }
6 哪些app可以使用某app提供的signature權限或signatureOrSystem權限?
根據上面的代碼,我們可以得出以下結論:
如果App A聲明了signatureOrSystem權限,即android:protectionLevel="signature|privileged"或者android:protectionLevel="signatureOrSystem",則可以使用該權限的app包括:
(1)與App A有相同簽名的app
(2)與系統簽名相同的app,即與廠商簽名(廠商ROM中的系統app簽名)相同的app
(3)在/system/priv-app目錄中的app,不管是否滿足(1)和(2)的條件。即任意app只要放到了/system/priv-app就可以使用App A的signatureOrSystem級別的權限。
如果App A聲明了signature權限,即android:protectionLevel="signature",則可以使用該權限的app包括:
(1)與App A有相同簽名的app
(2)與系統簽名相同的app,即與廠商簽名(廠商ROM中的系統app簽名)相同的app
注意:與系統簽名相同,即與廠商簽名相同,這是指廠商推出的app,這些app有很大的訪問權限。
7 關於pre23的權限
在framework/base/core/res/AndroidManifest.xml中,有2個權限從normal級別提升到signature級別,它們是
android.permission.WRITE_SETTINGS
android.permission.SYSTEM_ALERT_WINDOW
可以預見,當市場上絕大多數app的targetSdkVersion為23或23以上(即Android 6.0或以上)時,pre23字樣將會被去掉,到那時這兩個權限將會得到更好地保護。
pre23的含義是,如果某個app的targetSdkVersion是22或者22以下(即Android 5.x及以下),那么該app就可以獲取到這2個權限。
<permission android:name="android.permission.WRITE_SETTINGS" android:label="@string/permlab_writeSettings" android:description="@string/permdesc_writeSettings" android:protectionLevel="signature|preinstalled|appop|pre23" /> <permission android:name="android.permission.SYSTEM_ALERT_WINDOW" android:label="@string/permlab_systemAlertWindow" android:description="@string/permdesc_systemAlertWindow" android:protectionLevel="signature|preinstalled|appop|pre23|development" />
8 關於install權限和runtime權限
install權限:安裝時權限,是指在安裝app的時候,賦予app的權限。normal和signature級別的權限都是安裝時權限。不會給用戶提示界面,系統自動決定權限的賦予或拒絕。
runtime權限:運行時權限,是指在app運行過程中,賦予app的權限。這個過程中,會顯示明顯的權限授予界面,讓用戶決定是否授予權限。如果app的targetSdkVersion是22(Lollipop MR1)及以下,dangerous權限是安裝時權限,否則dangerous權限是運行時權限。
參考:grantPermissionsLPw()中的注釋。
9 官方文檔關於權限保護級別的說明
權限保護級別是指由android:protectionLevel指定的值。
9.1 <permission>的權限級別 android:protectionLevel
參考: https://developer.android.com/guide/topics/manifest/permission-element.html
Value Meaning
“normal” normal級別是默認值。低風險的權限采用此級別。在app安裝的時候,系統自動賦予此app請求的所有normal權限,而不會征求用戶的同意(但是,在安裝app之前,用戶總是有權選擇檢查這些權限)。
“dangerous” 較高風險的權限,此級別的權限意味着,請求權限的app將要訪問用戶的隱私數據或者控制設備,這可能給用戶帶來負面影響。
因為dangerous權限會引入潛在的風險,所以系統不會自動賦予此類權限給app。例如,在安裝app的時候,會將dangerous權限展示給用戶,並請求用戶確認。
“signature” 只有請求權限的app與聲明權限的app的簽名是一樣的時候,系統才會賦予signature權限。
如果簽名一致,系統會自動賦予權限,而不會通知用戶或者征求用戶的同意。
“signatureOrSystem” 系統賦予此類權限有2種情況:(1)請求權限的app與聲明權限的app的簽名一致;(2)請求權限的app在Android 系統鏡像(system image)中。
signatureOrSystem權限主要用在這個場景:多個軟件供應商的apps預裝到了系統目錄(system/priv-app)中,而且這些apps之間會共享一些功能。除此之外,盡量不要使用此類權限級別。
9.2 PermissionInfo中關於 protectionLevel
參考: https://developer.android.com/reference/android/content/pm/PermissionInfo.html
PermissionInfo.java中的權限保護級別主要有PROTECTION_NORMAL, PROTECTION_DANGEROUS, 或者 PROTECTION_SIGNATURE。
public static final int PROTECTION_NORMAL = 0;
public static final int PROTECTION_DANGEROUS = 1;
public static final int PROTECTION_SIGNATURE = 2;
除此之外,signature級別的權限還有一些附加的標志,例如,PROTECTION_FLAG_PRIVILEGED,PROTECTION_FLAG_APPOP或者PROTECTION_FLAG_PRE23 等等。
public static final int PROTECTION_FLAG_PRIVILEGED = 0x10;
public static final int PROTECTION_FLAG_DEVELOPMENT = 0x20;
public static final int PROTECTION_FLAG_APPOP = 0x40;
注意:PROTECTION_SIGNATURE_OR_SYSTEM 和PROTECTION_FLAG_SYSTEM已經被棄用。
public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
public static final int PROTECTION_FLAG_SYSTEM = 0x10;
9.3 R.attr.html中 protectionLevel
參考: https://developer.android.com/reference/android/R.attr.html#protectionLevel
這里涵蓋了protectionLevel所有可能的取值。
protectionLevel描繪了權限所蘊含的潛在風險,並且指明了系統在賦予某個app請求的權限時所要遵守的規程(procedure)。Android標准的權限有着事先定義好的、固定不變的protectionLevel(事實上,protectionLevel也會變,見pre23相關的說明)。
自定義的權限的protectionLevel屬性可以取下面列表中的‘Constant’值,如果有多個’Constant’值,則需要用‘|’分隔開。如果沒有定義protectionLevel,則默認權限為normal。
權限級別可以分為兩類:基礎權限級別和附加權限級別。
基礎權限級別
有3種:normal、dangerous和signature。(注:signatureOrSystem處於棄用狀態,不建議使用)
附加權限級別
0x10及其之后的權限級別都屬此類,例如,privileged、appop等。它們必須附加在基礎權限上。目前貌似只能附加在signature權限上。
Constant Value Description
normal 0 normal級別是默認值。低風險的權限采用此級別。在app安裝的時候,系統自動賦予此app請求的所有normal權限,而不會征求用戶的同意(但是,在安裝app之前,用戶總是有權選擇檢查這些權限)。
dangerous 1 較高風險的權限,此級別的權限意味着,請求權限的app將要訪問用戶的隱私數據或者控制設備,這可能給用戶帶來負面影響。
因為dangerous權限會引入潛在的風險,所以系統不會自動賦予此類權限給app。例如,在安裝app的時候,會將dangerous權限展示給用戶,並請求用戶確認。
signature 2 只有請求權限的app與聲明權限的app的簽名是一樣的時候,系統才會賦予signature權限。
如果簽名一致,系統會自動賦予權限,而不會通知用戶或者征求用戶的同意。
signatureOrSystem 3 系統賦予此類權限有2種情況:(1)請求權限的app與聲明權限的app的簽名一致;(2)請求權限的app在Android 系統鏡像(system image)中。
signatureOrSystem權限主要用在這個場景:多個軟件供應商的apps預裝到了系統目錄(system/priv-app)中,而且這些apps之間會共享一些功能。除此之外,盡量不要使用此類權限級別。
privileged 0x10 只能與signature同時使用。signature|privileged與signatureOrSystem意義相同。Android中system/priv-app目錄和system/framework目錄中的app可以訪問privileged權限。
system 0x10 意義與privileged相同。注意:由於system的概念會造成混亂,不建議使用。system權限並不是system/app目錄中的app能訪問的權限。
development 0x20 development applications可以訪問此權限。 Android標准權限中,development是與signature和privileged一起用的。例如android:protectionLevel="signature|privileged|development"
appop 0x40 由AppOpsManager來檢查app是否訪問此類權限。
pre23 0x80 此類權限自動被賦予那些targetSdkVersion在22(Android 5.x)或22以下的app。
installer 0x100 此類權限自動被賦予負責安裝apk的系統app。
verifier 0x200 此類權限自動被賦予負責驗證apk的系統app。
preinstalled 0x400 此類權限可以自動被賦予任何預安裝在system image中的app,不只是privileged app。
setup 0x800 此類權限自動被賦予‘安裝向導’app。
10 權限在四大組件中的使用以及URI權限
參考: https://developer.android.com/guide/topics/security/permissions.html
10.1 在AndroidManifest.xml使用權限保護組件
通過在AndroidManifest.xml中采用高保護級別的權限,來保護系統或者app的組件不會被隨意訪問。實現方式是,為組件添加android:permission屬性。
Activity 權限(用於 <activity> 標記) 限定了 哪些app可以啟動Activity。權限檢查是在 Context.startActivity() 和Activity.startActivityForResult()的時候進行的。如果調用者沒有此權限,則拋出SecurityException異常。
Service 權限(用於 <service> 標記) 限定了哪些app可以啟動(start)或者綁定(bind)服務。權限檢查是在Context.startService(), Context.stopService() 和Context.bindService()的時候進行的。如果調用者沒有此權限,則拋出SecurityException異常。
BroadcastReceiver 權限(用於<receiver> 標記) 限定了哪些app可以發送廣播給receiver。權限檢查是在Context.sendBroadcast()返回之后,在系統嘗試將廣播遞交給receiver的時候。即使調用者沒有權限,也不會拋出異常,只是不遞送intent(廣播)給receiver。
同理,在調用Context.registerReceiver()的時候,也可以加上權限,來控制誰可以發廣播給這個動態注冊的receiver。也可以在調用Context.sendBroadcast()的時候加上權限,來限定哪些BroadcastReceiver 允許接收這個廣播。
注意,可以同時給receiver(動態或靜態receiver)加上權限,並且給Context.sendBroadcast()加上權限,在這種情況下,需要雙方都驗證通過,才能完成信息傳遞。
注:動態receiver是指Context.registerReceiver()聲明的receiver。
靜態receiver是指AndroidManifest.xml中<receiver> 聲明的receiver。
ContentProvider 權限(用於 <provider> 標記) 限定了哪些app可以訪問ContentProvider中的數據。與其他組件不同,ContentProvider有2種權限android:readPermission和android:writePermission,分別控制讀和寫。
注意:如果provider同時受讀權限和寫權限保護,調用者只擁有寫權限,是不能夠執行讀操作的。
在獲取provider的時候,會進行權限檢查。如果沒有讀權限和寫權限,則拋出SecurityException異常。
在操作provider的時候,也會進行權限檢查。在這里操作provider是指增刪查改操作,讀寫文件。
查詢的時候(ContentResolver.query() 被調用),需要讀權限;添加、修改、刪除的時候(ContentResolver.insert(), ContentResolver.update(), ContentResolver.delete()被調用 ),需要寫權限。如果沒有相應的權限,則拋出SecurityException異常。
讀文件(openFile()時,mode值含有r),需要讀權限;寫文件(openFile()時,mode值含有w),需要寫權限。
具體都有哪些情況檢查讀寫權限,可以參考ContentProvider.java。
10.2 URI Permissions
URI 權限是對上面的權限的補充,是用這個場景中的:App A的content provider不能夠開放讀寫權限給App B(或者其他app),但是App A又需要傳遞一些數據給App B。在這個場景中,就可以使用URI權限。
典型的例子是,查看郵件app中的附件(例如圖片)。訪問郵件是受讀寫權限保護,因為是用戶的敏感數據。通過image viewer這個app查看郵件附件中的圖片,但是不能夠讓image viewer擁有讀取郵件的權限,這時,image viewer無法打開附件中的圖片。
解決辦法,就是使用URI權限。這也算是,給沒有讀寫權限的app關上一扇門,然后給那些app打開一扇窗。
當啟動activity 或者 返回結果給activity 的時候,調用者可以設置Intent.FLAG_GRANT_READ_URI_PERMISSION 和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION標志。.這時接收數據的activity(例如上面場景中的App B或者image viewer,為便於描述,在這里稱之為App B吧)就有權限訪問Intent中的URI指定的數據了,即使App B沒有讀寫content provider的權限。
注意:App B保持訪問特定URI的權限,不是永久的,只持續到App B應用退出之前或者 URI權限回收之前。
Content provider通過android:grantUriPermissions或者<grant-uri-permissions>來支持URI權限。
也可以在啟動Activity的時候,通過Context.grantUriPermission()賦予URI權限。Context.revokeUriPermission()可收回URI權限,Context.checkUriPermission()用來檢查URI權限。
11 如果targetSdkVersion版本低,那些在targetSdkVersion之后新加的權限就會被自動賦予
參考: https://developer.android.com/guide/topics/security/permissions.html#auto-adjustments
隨着時間的推移,Android中會對一些特定的API加上權限保護(應該是用新的權限對已有的API進行保護)。對於已經存在的app,以前調用這些API是不需要權限的,但是在新版本的Android系統中需要檢查權限。這個問題就是舊app運行在新系統上的問題。
為了讓舊app能夠在新版本的Android系統中正常的運行,Android會自動給這些app的manifest中加上相應的權限。何時給某個app加上缺失的權限,取決於targetSdkVersion。如果targetSdkVersion的值比添加新權限時的Api level低,那么Android將為其加上權限,即使該app並不需要此權限。
例如,WRITE_EXTERNAL_STORAGE權限是在API level 4的時候添加的,如果某個app的targetSdkVersion為3或者更小,那么當這個app運行在新版本的Android中時,自動會獲得WRITE_EXTERNAL_STORAGE權限。在這里,新版本的Android是指Api level為4或4以上。
在這種情況下,查看這個app請求的所有權限的時候,會看到Android自動為這個app添加的權限。(注:可以用命令 adb shell dumpsys package <包名>查看某個app所請求的全部權限。)
為了避免不必要的權限添加到你的app中,要經常更新targetSdkVersion,越大越好。查看Android每一個版本中都新加了哪些權限,請參考:https://developer.android.com/reference/android/os/Build.VERSION_CODES.html
12 Android版本演進過程中新添加的權限
注:一般的權限名的前綴為android.permission.,也有部分權限不是,例如UNINSTALL_SHORTCUT,完整權限為com.android.launcher.permission.UNINSTALL_SHORTCUT。
Api level Android 版本 新加的權限
4 Donut, 1.6 (2009.9) WRITE_EXTERNAL_STORAGE
READ_PHONE_STATE
CHANGE_WIFI_MULTICAST_STATE
GLOBAL_SEARCH
INSTALL_LOCATION_PROVIDER
5 Eclair, 2.0 (2009.11) ACCOUNT_MANAGER
8 Froyo, 2.2 (2010.6) BIND_DEVICE_ADMIN
BIND_WALLPAPER
KILL_BACKGROUND_PROCESSES
SET_TIME
9 Gingerbread, 2.3 (2010.11) NFC
SET_ALARM
USE_SIP
11 Honeycomb, 3.0 (2011.2) BIND_REMOTEVIEWS
14 Ice Cream Sandwich, 4.0 (2011.10) ADD_VOICEMAIL
BIND_TEXT_SERVICE
BIND_VPN_SERVICE
16 JellyBean, 4.1 (2012.6) READ_CALL_LOG
WRITE_CALL_LOG
BIND_ACCESSIBILITY_SERVICE
READ_EXTERNAL_STORAGE
注意:請求 WRITE_EXTERNAL_STORAGE權限的時候,將會自動獲取到READ_EXTERNAL_STORAGE權限。
18 JellyBean MR2, 4.3 (2013.7) BIND_NOTIFICATION_LISTENER_SERVICE
LOCATION_HARDWARE
SEND_RESPOND_VIA_MESSAGE
19 Kitkat, 4.4 (2013.10) BIND_NFC_SERVICE
BIND_PRINT_SERVICE
BLUETOOTH_PRIVILEGED
CAPTURE_AUDIO_OUTPUT
CAPTURE_SECURE_VIDEO_OUTPUT
CAPTURE_VIDEO_OUTPUT
INSTALL_SHORTCUT
MANAGE_DOCUMENTS
MEDIA_CONTENT_CONTROL
TRANSMIT_IR
UNINSTALL_SHORTCUT
20 Kitkat Watch, 4,4W (2014.6) BODY_SENSORS
21 Lollipop, 5.0 (2014.11) BIND_DREAM_SERVICE
BIND_TV_INPUT
BIND_VOICE_INTERACTION
READ_VOICEMAIL
WRITE_VOICEMAIL
22 Lollipop MR1, 5.1 (2015.3) BIND_CARRIER_MESSAGING_SERVICE
注意:此權限在Api level23的時候被棄用,采用BIND_CARRIER_SERVICES來代替。
23 Marshmallow, 6.0 ACCESS_NOTIFICATION_POLICY
BIND_CARRIER_SERVICES
BIND_CHOOSER_TARGET_SERVICE
BIND_INCALL_SERVICE
BIND_MIDI_DEVICE_SERVICE
BIND_TELECOM_CONNECTION_SERVICE
GET_ACCOUNTS_PRIVILEGED
PACKAGE_USAGE_STATS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
USE_FINGERPRINT
24 Nougat, 7.0 BIND_CONDITION_PROVIDER_SERVICE
BIND_QUICK_SETTINGS_TILE
BIND_SCREENING_SERVICE
BIND_VR_LISTENER_SERVICE
參考:https://developer.android.com/reference/android/os/Build.VERSION_CODES.html
完整權限名,請參考:https://developer.android.com/reference/android/Manifest.permission.html
Android版本與Api level的對應關系,請參考:https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels
