權(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)限
權(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)限組:
ADB工具
配合runtime permission, 也新增了一些相關(guān)的adb 命令:
查看所有的dangerous permissions: adb shell pm list permissions –g –d
安裝app并且對所有列在app manifest文件下的所有permission給予授權(quán): adb install -g <path_to_apk>
授權(quán)給某個app某個permission: adb pm grant <package_name> <permission_name>
撤銷授權(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)限",進入
Packageinstaller APP 權(quán)限修改界面
詳細列出應(yīng)用需要的權(quán)限
ManagePermissionsActivity.java
AppPermissionsFragment.java:
管理應(yīng)用權(quán)限類:
AppPermissions.java:
構(gòu)造函數(shù):
通過packageInof 獲得應(yīng)用所有權(quán)限暗甥,在封裝成permissiongroup撤防;
在AppPremissionsFragment中顯示Group
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
對于targetSdkVersion < 23的應(yīng)用
允許權(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
如果不是動態(tài)權(quán)限蓄诽,
1 更新AppOps 服務(wù)
2 更新permissionflag
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;
}