Android的權限管理越來越完善,但是牽涉的內容也是更多了:從4.4的AppOps到6.0的Runtime Permission,Google還是爲之做了不少努力。
AppOps簡介:
Android 4.4加入的權限管理:用戶在安裝應用時,會彈窗列舉申請的權限,用戶授權才能正常安裝,因此只要安裝了的應用就會獲取所有權限。部分三方OEM廠商會將安裝授予的權限改爲詢問,提高安全性。因爲是安裝授予的權限,我們暫且把AppOps管理的權限稱爲“安裝權限”。
雖然AppOps在4.4沒有對外開放,但仍是後續版本權限管理的基礎。
Runtime Permission簡介:
Android 6.0啓用的權限管理:在應用運行時,需要動態的申請權限,而不僅僅是在Manifest中申明就行了。依據其特性,我們稱之爲“運行時權限”。6.0中對於sdk >= 23的應用,安裝權限都是默認賦予的。
運行時權限與安裝權限是相輔相成的,只有同時具備兩種權限,應用才能正常使用對應功能。需要指出的是:運行時權限僅針對api >= 23的應用,對於api < 23的應用,仍沿用AppOps的規則。
權限的檢查:
在Activity中檢查應用是否有權限,ContextWrapper.java:
@Override
public int checkSelfPermission(String permission) {
return mBase.checkSelfPermission(permission);
}
其具體實現在ContextImpl.java中:
@Override
public int checkSelfPermission(String permission) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
return checkPermission(permission, Process.myPid(), Process.myUid());
}
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
try {
return ActivityManagerNative.getDefault().checkPermission(
permission, pid, uid);
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
}
走入了ActivityManagerNative.java中,ActivityManagerNative.getDefault()獲取到的是一個IActivityManager對象,這實際上是ActivityManagerService的一個遠程代理,具體實現在ActivityManagerService.java:
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, uid, -1, true);
}
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
如果是MY_PID,直接賦予權限,否則調用ActivityManager.java的checkComponentPermission方法:
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
// Should never happen, but if it does... deny!
Slog.e(TAG, "PackageManager is dead?!?", e);
}
return PackageManager.PERMISSION_DENIED;
}
ROOT_UID和SYSTEM_UID也是直接賦予權限的。在上面代碼中owningUid是設置爲-1的,因此最終會走到AppGlobals.getPackageManager()
.checkUidPermission(permission, uid),通過PackageManager來查詢權限,其具體實現在PackageManagerService.java
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
//依據uid查詢,獲取一個應用的狀態對象,判斷該應用是否具有該權限
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
final SettingBase ps = (SettingBase) obj;
final PermissionsState permissionsState = ps.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
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;
}
}
//此處處理系統應用的狀態,延伸下去發現是從xml文件讀取的默認權限。
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;
}
至此運行時權限的查詢流程走完了,我們發現運行時權限最終是落腳於PackageManagerService中的,這裏纔是運行時權限的核心。至於運行時權限的設置、持久化是如何進行,在下文權限的申請中討論,當然也是在PackageManagerService中。
權限的申請:
動態權限的申請用requestPermissions來進行,從Activity作爲起點:
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can reqeust only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
Intent的獲取是通過PackageManager來獲取的,用的是startActivityForResult方法來啓動權限申請對話框,便於返回結果:
public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
if (ArrayUtils.isEmpty(permissions)) {
throw new NullPointerException("permission cannot be null or empty");
}
Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
intent.setPackage(getPermissionControllerPackageName());
return intent;
}
intent的Action爲ACTION_REQUEST_PERMISSIONS,隱式的調用GrantPermissionsActivity來進行授權。UI部分在GrantPermissionsViewHandler中處理,具體授權仍在GrantPermissionActivity中,具體授權代碼如下:
@Override
public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
if (isObscuredTouch()) {
showOverlayDialog();
finish();
return;
}
GroupState groupState = mRequestGrantPermissionGroups.get(name);
if (groupState.mGroup != null) {
if (granted) {
groupState.mGroup.grantRuntimePermissions(doNotAskAgain);
groupState.mState = GroupState.STATE_ALLOWED;
} else {
groupState.mGroup.revokeRuntimePermissions(doNotAskAgain);
groupState.mState = GroupState.STATE_DENIED;
}
updateGrantResults(groupState.mGroup);
}
if (!showNextPermissionGroupGrantRequest()) {
setResultAndFinish();
}
}
我們看到grantRuntimePermissions是AppPermissionGroup的方法,這是PackageInstaller中的一個類,在6.0中運行時權限也都是PackageInstaller這個系統應用在管理。從名字我們也可以看出,這個應用也負責應用的安裝、卸載。
public boolean grantRuntimePermissions(boolean fixedByTheUser) {
final boolean isSharedUser = mPackageInfo.sharedUserId != null;
final int uid = mPackageInfo.applicationInfo.uid;
// We toggle permissions only to apps that support runtime
// permissions, otherwise we toggle the app op corresponding
// to the permission if the permission is granted to the app.
for (Permission permission : mPermissions.values()) {
if (mAppSupportsRuntimePermissions) {
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
return false;
}
// Ensure the permission app op enabled before the permission grant.
if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
permission.setAppOpAllowed(true);
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
}
// Grant the permission if needed.
if (!permission.isGranted()) {
permission.setGranted(true);
mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
permission.getName(), mUserHandle);
}
// Update the permission flags.
if (!fixedByTheUser) {
// Now the apps can ask for the permission as the user
// no longer has it fixed in a denied state.
if (permission.isUserFixed() || permission.isUserSet()) {
permission.setUserFixed(false);
permission.setUserSet(true);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_USER_SET,
0, mUserHandle);
}
}
} else {
// Legacy apps cannot have a not granted permission but just in case.
// Also if the permissions has no corresponding app op, then it is a
// third-party one and we do not offer toggling of such permissions.
if (!permission.isGranted() || !permission.hasAppOp()) {
continue;
}
if (!permission.isAppOpAllowed()) {
permission.setAppOpAllowed(true);
// It this is a shared user we want to enable the app op for all
// packages in the shared user to match the behavior of this
// shared user having a runtime permission.
if (isSharedUser) {
// Enable the app op.
String[] packageNames = mPackageManager.getPackagesForUid(uid);
for (String packageName : packageNames) {
mAppOps.setUidMode(permission.getAppOp(), uid,
AppOpsManager.MODE_ALLOWED);
}
} else {
// Enable the app op.
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
}
// Mark that the permission should not be be granted on upgrade
// when the app begins supporting runtime permissions.
if (permission.shouldRevokeOnUpgrade()) {
permission.setRevokeOnUpgrade(false);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
0, mUserHandle);
}
// Legacy apps do not know that they have to retry access to a
// resource due to changes in runtime permissions (app ops in this
// case). Therefore, we restart them on app op change, so they
// can pick up the change.
mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
}
}
}
return true;
}
permissionsState.grantRuntimePermission
與checkUidPermission中的permissionsState.hasPermission相呼應。
mSettings.writeRuntimePermissionsForUserLPr(userId, false)進行持久化,將結果寫入runtime-permissions.xml。
未完待續。。。
參考:
http://blog.csdn.net/lewif/article/details/49124757
http://mobile.51cto.com/android-526905.htm