Android許可權管理原理(含6.0-4.3)
Android 4.3-5.1 AppOpsManager動態許可權管理(官方不成熟的許可權管理)
AppOpsManager 是Google在Android4.3-Android5.0引入的動態許可權管理方式,但是又與Google覺得不成熟,所以在每個發行版的時候,總是會將這個功能給遮蔽掉。國內一些早期版本的許可權動態管理的表現類似,這裡用CyanogenMod12裡面的實現講述一下,國內的ROM原始碼拿不到,不過從表現來看,實現應該類似。
在一開始,其實Google將許可權的動態管理放在每個服務內部,類似於登記的策略,比如,如果一個App要申請Cammera許可權,android6.0之前是不要顯示的動態申請的,只需要在Manifest中宣告即可,在需要使用Camera的時候,如果是第一,App一定沒有在Camera服務中登記過,這時候,Camera服務就會呼叫AppOpsManager訪問AppOpsService,發起許可權申請請求,並彈出統一的許可權申請對話方塊,並做好登記,如果獲得了許可權,就開啟所請求的服務,如果被拒絕,就拒絕向APP提供服務。這個時機是在AppOpsManager鑑定的時候,類似於對沒有獲得許可權的APP進行服務攔截,如果在Setting裡設定了響應的許可權,也會去更新相應的許可權操作持久化檔案/data/system/appops.xml,下次再次申請服務的時候,服務會再次鑑定許可權。
舉個栗子(定位服務LocationManagerService)
CM12 原始碼
在呼叫LocationManager的requestLocationUpdates函式獲取定位資訊時候,其實是通過Binder請求LocationManagerService去定位。
LocationManager
private void requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper, PendingIntent intent) { String packageName = mContext.getPackageName(); // wrap the listener class ListenerTransport transport = wrapListener(listener, looper); try { mService.requestLocationUpdates(request, transport, intent, packageName); } catch (RemoteException e) { Log.e(TAG, "RemoteException", e); } }
看一下LocationManagerService中關鍵程式碼
LocationManagerService
@Override public void requestLocationUpdates(LocationRequest request, ILocationListener listener, PendingIntent intent, String packageName) { if (request == null) request = DEFAULT_LOCATION_REQUEST; checkPackageName(packageName); <!--關鍵函式 1 ,查詢Manifest檔案,是否進行了許可權宣告 --> int allowedResolutionLevel = getCallerAllowedResolutionLevel(); checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, request.getProvider()); 。。。 <!--獲取呼叫app的pid跟uid--> final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); try { <!--關鍵函式 2 檢查是否動態授權了許可權,或者拒絕了許可權--> checkLocationAccess(uid, packageName, allowedResolutionLevel); synchronized (mLock) { Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, packageName, workSource, hideFromAppOps); if (receiver != null) { requestLocationUpdatesLocked(sanitizedRequest, receiver, pid, uid, packageName); } } } finally { Binder.restoreCallingIdentity(identity); } }
getCallerAllowedResolutionLevel主要通過呼叫getAllowedResolutionLevel,通過mContext.checkPermission查詢APP是否在Manifest中聲明瞭許可權
private int getCallerAllowedResolutionLevel() {
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
pid, uid) == PackageManager.PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_FINE;
} else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
pid, uid) == PackageManager.PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_COARSE;
} else {
return RESOLUTION_LEVEL_NONE;
}
}
checkLocationAccess這裡才是動態鑑權的入口,在checkLocationAccess函式中,會呼叫mAppOps.checkOp去鑑權,mAppOps就是AppOpsManager例項,
boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
int mode = mAppOps.checkOp(op, uid, packageName);
if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_ASK ) {
return false;
}
}
return true;
}
進而通過Binder向AppOpsService服務傳送鑑權請求
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程式碼
@Override
public int noteOperation(int code, int uid, String packageName) {
final Result userDialogResult;
verifyIncomingUid(uid);
verifyIncomingOp(code);
synchronized (this) {
Ops ops = getOpsLocked(uid, packageName, true);
...
<!--關鍵點 1-->
if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
switchOp.mode == AppOpsManager.MODE_ERRORED) {
op.rejectTime = System.currentTimeMillis();
op.ignoredCount++;
return switchOp.mode;
<!--關鍵點 2-->
} else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {
op.time = System.currentTimeMillis();
op.rejectTime = 0;
op.allowedCount++;
return AppOpsManager.MODE_ALLOWED;
} else {
op.noteOpCount++;
<!--關鍵函式 3-->
userDialogResult = askOperationLocked(code, uid, packageName,
switchOp);
}
}
return userDialogResult.get();
}
在上面的程式碼裡面,1、2是對已經處理過的場景直接返回已授權,或者已經拒絕,而3 就是我們常見授權入口對話方塊,這裡是統一在AppOpsServie中進行授權處理的。askOperationLocked會顯示一個系統對話方塊,使用者選擇授權或者拒絕後,AppOpsServie會將選擇記錄在案,並通知申請服務提供或者拒絕服務。askOperationLocked通過mHandler傳送鑑權Message,看一下實現其實就是新建了一個PermissionDialog授權對話方塊,並且將AppOpsService的引用傳了進去,授權後會通過mService.notifyOperation通知授權結果。
mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PERMISSION_DIALOG: {
HashMap<String, Object> data =
(HashMap<String, Object>) msg.obj;
synchronized (this) {
Op op = (Op) data.get("op");
Result res = (Result) data.get("result");
op.dialogResult.register(res);
if(op.dialogResult.mDialog == null) {
Integer code = (Integer) data.get("code");
Integer uid = (Integer) data.get("uid");
String packageName =
(String) data.get("packageName");
Dialog d = new PermissionDialog(mContext,
AppOpsService.this, code, uid,
packageName);
op.dialogResult.mDialog = (PermissionDialog)d;
d.show();
}
}
}break;
}
}
};
Android發行版原始碼對於動態許可權管理的支援(幾乎為零)
雖然App可以獲得AppOpsManager的例項,但是真正操作動態許可權的介面setMode函式是被隱藏,如下
/** @hide */
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
}
}
在Android4.3-5.0之間,setMode也是不對外開放的,看原始碼也只有NotificationManagerService這個系統應用使用了setMode,也就是說發行版,只有通知是通過系統的通知管理進行動態管理的。
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
checkCallerIsSystem();
Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
}
}
對於6.0以下的手機許可權如何檢測
對於Android6.0以下的手機,不需要關心targetVersion。先說個自己驗證的結果:基本沒法檢測,同時也不需要檢測,就算檢測出來也沒有多大意義,因為,觸發時機是在真正的呼叫服務時候。對於4.3到6.0之前的國產ROM,雖然採用AppopsManagerService,但是並未按照Google的模型對所有許可權進行適配,在這個模型下,也就適配了兩個許可權,(懸浮窗還不一定有)
- 通知許可權 public static final int OP_POST_NOTIFICATION = 11;
- 懸浮窗許可權 public static final int OP_SYSTEM_ALERT_WINDOW = 24;
Google發行版的APPOpsService,基本是把整個鑑權邏輯給遮蔽了,通過CM的原始碼,課對這部分程式碼窺探一斑,如果整個許可權都採用4.3許可權管理模型,在拒絕一項許可權的時候,這個操作會被持久化到appops.xml中去,但是具體看下去,其實並不是如此,這種機制只對以上兩個許可權生效:
<pkg n="com.xxx">
<uid n="10988">
<!--關鍵點1-->
<op n="11" m="1" t="1513145979969" r="1521550658067" />
<op n="12" t="1521550651593" />
<op n="29" t="1521550682769" />
<pkg n="com.wandoujia.phoenix2.usbproxy">
<uid n="10969">
<op n="4" t="1517279031173" />
<!--關鍵點2-->
<op n="11" m="1" t="1510889291834" r="1517279030708" />
<op n="14" t="1517293452801" />
<!--關鍵點3-->
<op n="24" m="1" />
<op n="40" t="1513599239364" d="600011" />
國產rom中,假如你拒絕授權位置許可權,按照AppOpsService模型,該操作應該被持久化到appops.xml中去,但是,結果並非如此,也就是說,對於其他許可權,國產ROM應該是自己糊弄了一套持久管理,持久化Android系統API無法訪問的地方,僅僅為自身ROM可見。appops.xml真正被系統使用時從Android6.0開始,其實Android6.0是有兩套許可權管理的,這其實很混亂,不知道Google怎麼想的,不過6.0似乎也有漏洞:許可權的授予跟回收許可權好像並不配對。
那麼這就帶來了一個問題,在Android4.3到Android6.0之間的版本,並沒有同一個API來檢測是否獲取了某種許可權,因為你動態更新的許可權並未持久化到appops.xml中去。對於Android6.0之前的ROM,雖然不能檢測,但完全可以直接用服務,不會崩潰,因為如果真需要鑑權,它的鑑權時機其實是在服務使用的時候。AppopsManager在6.0之前,只能用來檢測通知,可能還有懸浮窗。
Android 6.0許可權管理原理
Android6.0的動態許可權管理讓使用者在任何時候都可以取消授權,因此,每次在使用系統服務的時候,都要動態查詢是否獲取了相應的許可權,如果沒有獲取,就需要動態去申請。
Android6.0許可權查詢
V4相容包裡面提供了一個工具類PermissionChecker,可以用來檢查許可權獲取情況。
PermissionChecker
public static int checkPermission(@NonNull Context context, @NonNull String permission,
int pid, int uid, String packageName) {
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
String op = AppOpsManagerCompat.permissionToOp(permission);
if (op == null) {
return PERMISSION_GRANTED;
}
if (packageName == null) {
String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
if (packageNames == null || packageNames.length <= 0) {
return PERMISSION_DENIED;
}
packageName = packageNames[0];
}
if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
!= AppOpsManagerCompat.MODE_ALLOWED) {
return PERMISSION_DENIED_APP_OP;
}
return PERMISSION_GRANTED;
}
這裡我們只關心context.checkPermission,從上面對於4.3-5.1的APPOpsManager的分析,我們知道AppOpsManagerCompat本身的一些操作對於許可權管理並沒有實際意義,只是用來做一些標記,最多就是對於統治許可權有些用。接下來看checkPermission:
ContextImple.java
/** @hide */
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
try {
return ActivityManagerNative.getDefault().checkPermissionWithToken(
permission, pid, uid, callerToken);
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
}
接著往下看
ActivityManagerNative.java
public int checkPermission(String permission, int pid, int uid)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(permission);
data.writeInt(pid);
data.writeInt(uid);
mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
data.recycle();
reply.recycle();
return res;
}
ActivityManagerService
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
}
進而呼叫ActivityManager.checkComponentPermission,呼叫AppGlobals.getPackageManager().checkUidPermission(permission, uid);
ActivityManager.java
/** @hide */
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
<!--root及System能獲取所有許可權-->
if (uid == 0 || uid == Process.SYSTEM_UID) {
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;
}
最終呼叫PackageManagerService.java去檢視是否有許可權。到這裡,我們只需要知道許可權的查詢其實是通過PKMS來進行的。心裡先有個底,許可權的更新,持久化,恢復都是通過PKMS來進行的。
PKMS不同版本的許可權查詢
Android5.0的checkUidPermission
public int checkUidPermission(String permName, int uid) {
final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
synchronized (mPackages) {
<!--PackageManagerService.Setting.mUserIds陣列中,根據uid查詢uid(也就是package)的許可權列表-->
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
GrantedPermissions gp = (GrantedPermissions)obj;
if (gp.grantedPermissions.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
<!--mSystemPermissions記錄一些系統級的應用的 uid 對應的 permission->
HashSet<String> perms = mSystemPermissions.get(uid);
if (perms != null && perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
}
if (!isPermissionEnforcedLocked(permName, enforcedDefault)) {
return PackageManager.PERMISSION_GRANTED;
}
}
return PackageManager.PERMISSION_DENIED;
}
Android6.0+的checkUidPermission
@Override
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
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;
}
} 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;
}
可以看到Android6.0之後,對許可權的操作是PermissionsState
PermissionsState.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)
public boolean hasPermission(String name, int userId) {
enforceValidUserId(userId);
if (mPermissions == null) {
return false;
}
PermissionData permissionData = mPermissions.get(name);
return permissionData != null && permissionData.isGranted(userId);
}
從上面的程式碼可以很清晰看出,6.0之後,除了聲明瞭許可權之外,還必須是授權了的。前一篇文章,已經說明了執行時許可權跟install許可權區別,對於install許可權isGranted一直返回是True。這裡先不必深究PermissionsState是怎麼存進記憶體,你先記住,後面會將講。
Android6.0動態申請許可權
申請許可權可以通過V4包裡面的ActivityCompat,它已經對不同版本做了相容
ActivityCompat.java
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode) {
if (Build.VERSION.SDK_INT >= 23) {
ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);
} else if (activity instanceof OnRequestPermissionsResultCallback) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
final int[] grantResults = new int[permissions.length];
PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();
final int permissionCount = permissions.length;
for (int i = 0; i < permissionCount; i++) {
grantResults[i] = packageManager.checkPermission(
permissions[i], packageName);
}
((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
});
}
}
可以看到,如果是6.0以下,直接通過PKMS查詢是否在Manifest裡面申請了許可權,並把查詢結果通過onRequestPermissionsResult回撥傳給Activity或者Fragment。其實這裡只要在Manifest中聲明瞭,就會預設是Granted。接著往下看:ActivityCompatApi23最終會呼叫activity.requestPermissions去請求許可權。
Activity
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}
Intent其實是通過PackageManager(ApplicationPackageManager實現類)獲取的Intent
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;
}
這裡首先是隱式的獲取授權Activity元件相關資訊(GrantPermissionsActivity),其實就是對話方塊樣式的授權Activity,它是PackageInstaller系統應用裡面的一個Activity。這裡的getPermissionControllerPackageName其實就是獲取相應的包名,
ApplicationPackageManager.java (android-6.0\frameworks\base\core\java\android\app)
@Override
public String getPermissionControllerPackageName() {
synchronized (mLock) {
if (mPermissionsControllerPackageName == null) {
try {
mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
return mPermissionsControllerPackageName;
}
}
最終通過PackageManagerService獲取包名
PackageManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)
@Override
public String getPermissionControllerPackageName() {
synchronized (mPackages) {
return mRequiredInstallerPackage;
}
}
mRequiredInstallerPackage這個變數具體賦值是在PMS的構造器中:對於原生Android 6.0,許可權管理的APP跟安裝器是同一個
mRequiredInstallerPackage = getRequiredInstallerLPr();
這裡會得到PackageInstaller應用的相關資訊,PackageInstaller負責應用的安裝與解除安裝,裡面還包含了對授權管理的一些邏輯。startActivityForResult啟動的就是PackageInstaller中的GrantPermissionsActivity,該Activity主要負責許可權的授予工作。
<activity android:name=".permission.ui.GrantPermissionsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true"
android:theme="@style/GrantPermissions">
<intent-filter>
<action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
這是一個類似於對話方塊的懸浮窗樣式的Activity
<style name="GrantPermissions" parent="Settings">
<item name="android:windowIsFloating">true</item>
<item name="android:windowElevation">@dimen/action_dialog_z</item>
<item name="android:windowSwipeToDismiss">false</item>
</style>
之後就是動態更新許可權流程:
如何動態更新RuntimePermission
通過上面的流程,我們進入了GrantPermissionsActivity
GrantPermissionsActivity
GrantPermissionsActivity其實是利用GroupState物件與PKMS通訊,遠端更新許可權的。
public class GrantPermissionsActivity extends OverlayTouchActivity
implements GrantPermissionsViewHandler.ResultListener {
private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>();
....
@Override
public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
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();
}
}
具體更新流程:
public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
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 (filterPermissions != null
&& !ArrayUtils.contains(filterPermissions, permission.getName())) {
continue;
}
...
<!--一些關鍵點-->
// 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);
可以看到最終還是呼叫PackageManager去更新App的執行時許可權,最終走進PackageManagerService服務,
PackageManagerService
@Override
public void grantRuntimePermission(String packageName, String name, final int userId) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
...一些檢查
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"grantRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"grantRuntimePermission");
。。。。。
...
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final PermissionsState permissionsState = sb.getPermissionsState();
...
...授權
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;
}
mOnPermissionChangeListeners.onPermissionsChanged(uid);
<!--持久化-->
// Not critical if that is lost - app has to request again.
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(PackageParser.Package pkg,
BasePermission bp) {
int index = pkg.requestedPermissions.indexOf(bp.name);
if (index == -1) {
throw new SecurityException("Package " + pkg.packageName
+ " has not requested permission " + bp.name);
}
if (!bp.isRuntime() && !bp.isDevelopment()) {
throw new SecurityException("Permission " + bp.name
+ " is not a changeable permission type");
}
}
首先要更新記憶體中的許可權授予情況
PermissionsState.java
private int grantPermission(BasePermission permission, int userId) {
if (hasPermission(permission.name, userId)) {
return PERMISSION_OPERATION_FAILURE;
}
final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
PermissionData permissionData = ensurePermissionData(permission);
if (!permissionData.grant(userId)) {
return PERMISSION_OPERATION_FAILURE;
}
if (hasGids) {
final int[] newGids = computeGids(userId);
if (oldGids.length != newGids.length) {
return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
}
}
return PERMISSION_OPERATION_SUCCESS;
}
private PermissionData ensurePermissionData(BasePermission permission) {
if (mPermissions == null) {
mPermissions = new ArrayMap<>();
}
PermissionData permissionData = mPermissions.get(permission.name);
if (permissionData == null) {
permissionData = new PermissionData(permission);
mPermissions.put(permission.name, permissionData);
}
return permissionData;
}
下一步,要將更新的許可權持久化到檔案中去 mSettings.writeRuntimePermissionsForUserLPr
RuntimePermission持久化
Settings.java
public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
if (sync) {
mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
} else {
mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
}
}
Settings.getPackageLPw這個方法,這是在安裝應用掃描的時候scanPackageDirtyLI方法呼叫的,裡面可以看到Settings類中的mUserIds、mPackages裡面存的value還有PackageManagerService中的mPackages.pkg. mExtras都是同一個玩意奏是個PackageSetting。
private File getUserRuntimePermissionsFile(int userId) {
// TODO: Implement a cleaner solution when adding tests.
// This instead of Environment.getUserSystemDirectory(userId) to support testing.
File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}
在目錄data/system/0/runtime-permissions.xml存放需要執行時申請的許可權,Android6.0以上才有
<pkg name="com.snail.labaffinity">
<item name="android.permission.CALL_PHONE" granted="true" flags="0" />
<item name="android.permission.CAMERA" granted="false" flags="1" />
</pkg>
RuntimePermission 恢復(其實這裡也包含普通許可權)
這些持久化的資料會在手機啟動的時候由PMS讀取,開機啟動,PKMS掃描Apk,並更新package資訊,檢查/data/system/packages.xml是否存在,這個檔案是在解析apk時由writeLP()建立的,裡面記錄了系統的permissions,以及每個apk的name,codePath,flags,ts,version,uesrid等資訊,這些資訊主要通過apk的AndroidManifest.xml解析獲取,解析完apk後將更新資訊寫入這個檔案並儲存到flash,下次開機直接從裡面讀取相關資訊新增到記憶體相關列表中,當有apk升級,安裝或刪除時會更新這個檔案,packages.xml放的只包括installpermission,runtimepermissiono由runtime-permissions.xml存放。
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
....
mSettings = new Settings(mPackages);
//彙總並更新和Permission相關的資訊
updatePermissionsLPw(null, null, true,
regrantPermissions,regrantPermissions);
//將資訊寫到package.xml、package.list及package-stopped.xml檔案中
mSettings.writeLPr();
....
mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));
Settings(File dataDir, Object lock) {
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
<!--載入package資訊-->
根據SettingsFile或者BackupSettingsFile讀取相應的設定資訊 生成PackageSetting物件,裡面有許可權列表欄位protected final PermissionsState mPermissionsState;,之後再執行中,動態許可權的操作都是針對這個物件
boolean readLPw(@NonNull List<UserInfo> users) {
FileInputStream str = null;
if (mBackupSettingsFilename.exists()) {
try {
str = new FileInputStream(mBackupSettingsFilename);
mReadMessages.append("Reading from backup settings file\n");
...
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
String tagName = parser.getName();
if (tagName.equals("package")) {
!--讀取package資訊,包括install許可權資訊(對於Android6.0package.xml)-->
readPackageLPw(parser);
...
<!--讀取runtime許可權資訊-->
for (UserInfo user : users) {
mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
}
}
private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
String name = null;
...
(tagName.equals(TAG_PERMISSIONS)) {
readInstallPermissionsLPr(parser,
packageSetting.getPermissionsState());
之後就可以checkpermission了
@Override
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
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;
}
原來的許可權存放位置在哪?不會都從Android Manifest清單去讀取,只會在啟動時讀取一次。Android6.0之前會吧所有的許可權都放置在data/system/packages.xml檔案中。Android6.0之後,分為執行時許可權跟普通許可權,普通許可權還是放在data/system/packages.xml中,執行時許可權防止在data/system/users/0/runtime-permissions.xml檔案中。根據執行時是否動態申請去更新許可權。
Android許可權管理的關鍵節點,在哪裡?
關鍵節點並不是查詢是否具有該許可權,Android6.0之前的 許可權查詢是不會觸發許可權申請與授權的,只有在請求系統服務的時候,由系統服務呼叫AppopsManager去查詢是否賦予了該許可權,第一次未操作肯定是null,未賦予就可能會觸發許可權申請邏輯,這個點在各個系統服務內部,由AppOpsService服務統一管理,不過對於官方的Release版本,其實只有系統通知APP才有動態許可權管理的能力,其他都沒有操作能力。