人生处万类,知识最为贤。这篇文章主要讲述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;
<
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
GrantPermissionsActivityGrantPermissionsActivity其实是利用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才有动态权限管理的能力, 其他都没有操作能力。
参考文档 【Android权限管理原理(含6.0-4.3)】1、Android 安全機制概述 Permission
2、android permission权限与安全机制解析
3、android6.0权限管理原理
4、深入理解 PackageManagerService
5、Android 4.3 隐藏功能 App Ops 分析
6、Android 权限机制, 你真的了解吗?
7、Android原生权限管理: AppOps
8、 Android 5.1 AppOps总结
9、[CM12源码]
推荐阅读
- Android - 框架之ButterKnife的使用
- android 4.4/5.1上使用aar的问题
- Android之图片加载框架Fresco基本使用
- Android - V之SwipeRefreshLayout的使用
- Android - 框架之Retrofit的使用
- Scala Varargs可变参数用法详细介绍和示例
- 算法题(使用递归生成所有可能的子序列)
- Bash程序检查Number是否为质数
- Python OpenCV仿射变换实现详细指南