###安卓权限预置设置
因为最近要在系统里做权限预置的功能,就进行了了解
####Code Baseline
https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
####核心代码类:
DefaultPermissionGrantPolicy.java
TODO:
阅读PermissionGrantedManagerService
阅读在activity申请授权的流程
阅读AppOpsManager
阅读UserHandle
从安卓6.0引入了运行时权限管理:RuntimePermission,对用户申请的权限,在运行时进行授予用户所需的权限,在运行时授予权限
但是可以发现部分系统应用是不需要启动时申请权限的,为了达成和系统应用相同的效果,即在运行时不默认弹出授权窗口的功能,需要对现有代码进行一些改动
通过网上查询,定位到的文件是DefaultPermissionGrantPolicy.java。
DefaultPermissionGrantPolicy.java位于/frameworks/base/services/core/java/com/android/server/pm/permission中,根据路径可以知道,这个路径下的所有内容都是和权限有关系的,之后会逐步阅读。
在文件的开头,是一些权限set。大家可能还记得,在早期,每个应用都需要以次独立的授予Manifest申请的所有权限,如果权限申请的过多,用户需要授予可能多达十数次权限,是很不友好的,所以之后安卓做了改动,按照权限组对应用的权限进行授权,这些set就是预置的权限组,将逻辑上相同的权限分为一组,在申请授权的时候同时授权。
之后一些packageProvider的操作,PackagesProvider是在PackageManagerInternal中声明的接口,代码如下
/**
* Provider for package names.
*/
public interface PackagesProvider {
/**
* Gets the packages for a given user.
* @param userId The user id.
* @return The package names.
*/
public String[] getPackages(int userId);
}
用于设置不同场景的包名,之后在系统默认应用权限授予的时候会用到。
接下来就是这个类的核心代码了
public void grantDefaultPermissions(int userId) {
grantPermissionsToSysComponentsAndPrivApps(userId);
grantDefaultSystemHandlerPermissions(userId);
grantDefaultPermissionExceptions(userId);
synchronized (mLock) {
mDefaultPermissionsGrantedUsers.put(userId, userId);
}
}
可以很清楚的知道,在grantDefaultPermissions中,按照不同的应用类型,分为系统组件和私有APP,处理系统消息的APP,额外处理的权限应用,分别按照用户id对权限进行权限授予
####系统APP的权限授予
首先查看grantPermissionsToSysComponentsAndPrivApps的功能,代码如下:
private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
Log.i(TAG, "Granting permissions to platform components for user " + userId);
List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser(
DEFAULT_PACKAGE_INFO_QUERY_FLAGS, UserHandle.USER_SYSTEM);
for (PackageInfo pkg : packages) {
if (pkg == null) {
continue;
}
if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
|| !doesPackageSupportRuntimePermissions(pkg)
|| ArrayUtils.isEmpty(pkg.requestedPermissions)) {
continue;
}
grantRuntimePermissionsForSystemPackage(userId, pkg);
}
}
逻辑也很简单,使用
List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser(
DEFAULT_PACKAGE_INFO_QUERY_FLAGS, UserHandle.USER_SYSTEM);
获取所有的系统package,并且调用grantRuntimePermissionsForSystemPackage(userId, pkg);
对权限进行授权,doesPackageSupportRuntimePermissions(pkg)
和grantRuntimePermissionsForSystemPackage(userId, pkg);
会稍后分析
####处理系统消息的APP的权限获取
因为安卓开源的风格,系统一些默认的APP是可以被替换的,比如短信APP,地图APP,甚至拨打电话的APP都是可以替换的,厂商也都会对这些APP进行替换,针对这些APP,grantDefaultSystemHandlerPermissions会提供给这些APP一些权限。
这些APP既不属于系统的应用,却在系统中有非常重要的作用。所以要单独处理
代码可能会比较长,但是逻辑却很清晰,也是分为获取package,之后授权两步。获取package的方式分为两种,其中一种是通过预置的PackageProvider找到处理这些APP的包名,获取package;另一种是通过Intent查询包名,获取package,之后都会调用```grantPermissionsToPackage``授予预设的权限组的权限。
####处理额外APP的系统权限
因为系统中还有其他的授权APP需要增加,相较于7.0的源码,安卓9.0新增了grantDefaultPermissionExceptions(userId)
方法,提供给一些包括预装应用或是后门应用
代码如下
private void grantDefaultPermissionExceptions(int userId) {
synchronized (mLock) {
if (mGrantExceptions == null) {
mGrantExceptions = readDefaultPermissionExceptionsLocked();
}
}
Set<String> permissions = null;
final int exceptionCount = mGrantExceptions.size();
for (int i = 0; i < exceptionCount; i++) {
String packageName = mGrantExceptions.keyAt(i);
PackageInfo pkg = getSystemPackageInfo(packageName);
List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
final int permissionGrantCount = permissionGrants.size();
for (int j = 0; j < permissionGrantCount; j++) {
DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
if (!isPermissionDangerous(permissionGrant.name)) {
continue;
}
if (permissions == null) {
permissions = new ArraySet<>();
} else {
permissions.clear();
}
permissions.add(permissionGrant.name);
grantRuntimePermissions(pkg, permissions, permissionGrant.fixed,
permissionGrant.whitelisted, true /*whitelistRestrictedPermissions*/,
userId);
}
}
}
可看出也是同样的逻辑,获取包名,获取权限,之后授予权限,可以看到这里有一个新的方法是isPermissionDangerous(permissionGrant.name),这个方法也是在9.0之后新增的,在安卓9.0,进一步将权限分为危险权限和非危险权限,比如网络权限就是非危险权限,会默认授予,其他权限会在运行时弹窗
在grantDefaultPermissionExceptions方法中需要授予权限的包集合为mGrantExceptions,通过readDefaultPermissionExceptionsLocked()获取权限。
private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
readDefaultPermissionExceptionsLocked() {
File[] files = getDefaultPermissionFiles;
if (files == null) {
return new ArrayMap<>(0);
}
for (File file : files) {
...
try (
InputStream str = new BufferedInputStream(new FileInputStream(file))
) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(str, null);
parse(parser, grantExceptions);
} catch (XmlPullParserException | IOException e) {
}
}
return grantExceptions;
}
private File[] getDefaultPermissionFiles() {
ArrayList<File> ret = new ArrayList<File>();
File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
Collections.addAll(ret, dir.listFiles());
dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
Collections.addAll(ret, dir.listFiles());
dir = new File(Environment.getOdmDirectory(), "etc/default-permissions");
Collections.addAll(ret, dir.listFiles());
dir = new File(Environment.getProductDirectory(), "etc/default-permissions");
Collections.addAll(ret, dir.listFiles());
dir = new File(Environment.getSystemExtDirectory(), "etc/default-permissions");
Collections.addAll(ret, dir.listFiles());
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
}
return ret.isEmpty() ? null : ret.toArray(new File[0]);
}
即从Environment.getRootDirectory()、Environment.getVendorDirectory()、Environment.getOdmDirectory()、Environment.getProductDirectory()、Environment.getSystemExtDirectory()的"etc/default-permissions"读取xml文件,并且生成了一个map,从map中读取包名以及所需额权限,进行授予。
####权限授予与运行时权限授予
从上述的代码中我们可以发现整个权限授予的流程都是:获取一个package,调用grantRuntimePermissions进行权限授予,以下是源代码的分析
在9.0的代码中,需要讨论的情况比较多,包括对权限分别授予,以及危险权限的授予
之后获取包名,获取package,判断package所需权限是否为空之后,如果APP包版本小于splitPerm.getTargetSdk(),将分别授予的权限分别添加到Permissions set中
// Automatically attempt to grant split permissions to older APKs
final List<PermissionManager.SplitPermissionInfo> splitPermissions =
mContext.getSystemService(PermissionManager.class).getSplitPermissions();
final int numSplitPerms = splitPermissions.size();
for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
final PermissionManager.SplitPermissionInfo splitPerm =
splitPermissions.get(splitPermNum);
if (applicationInfo != null
&& applicationInfo.targetSdkVersion < splitPerm.getTargetSdk()
&& permissionsWithoutSplits.contains(splitPerm.getSplitPermission())) {
permissions.addAll(splitPerm.getNewPermissions());
}
}
```
在最新的安卓版本中,对后台权限进行了控制,比如定位权限,如果APP退到后台,仍然需要定位权限,则需要授予后台定位权限,这部分就是保证首先授予定位权限,之后授予后台定位权限
```java
// Sort requested permissions so that all permissions that are a foreground permission (i.e.
// permissions that have a background permission) are before their background permissions.
final String[] sortedRequestedPermissions = new String[numRequestedPermissions];
int numForeground = 0;
int numOther = 0;
for (int i = 0; i < numRequestedPermissions; i++) {
String permission = requestedPermissions[i];
if (getBackgroundPermission(permission) != null) {
sortedRequestedPermissions[numForeground] = permission;
numForeground++;
} else {
sortedRequestedPermissions[numRequestedPermissions - 1 - numOther] =
permission;
numOther++;
}
}
还有一种极端情况需要考虑,即APP升级的情况,一个APP升级前已经授予了一些权限,升级时新增了一些权限的申请,那在升级之后就有两部分权限需要处理,一部分是升级前已经授予了的权限,一部分是新授予的权限,考虑到之前的前台权限和后台权限,需要进行额外的处理。
if (pm.checkPermission(fgPerm, pkg.packageName)
== PackageManager.PERMISSION_GRANTED) {
// Upgrade the app-op state of the fg permission to allow bg access
// TODO: Dont' call app ops from package manager code.
mContext.getSystemService(AppOpsManager.class).setUidMode(
AppOpsManager.permissionToOp(fgPerm), uid,
AppOpsManager.MODE_ALLOWED);
break;
}
最后就是权限的授予了,
int appOp = AppOpsManager.permissionToOpCode(permission);
if (appOp != AppOpsManager.OP_NONE
&& AppOpsManager.opToDefaultMode(appOp)
!= AppOpsManager.MODE_ALLOWED) {
// Permission has a corresponding appop which is not allowed by default
// We must allow it as well, as it's usually checked alongside the
// permission
if (DEBUG) {
Log.i(TAG, "Granting OP_" + AppOpsManager.opToName(appOp)
+ " to " + pkg.packageName);
}
mContext.getSystemService(AppOpsManager.class).setUidMode(
appOp, pkg.applicationInfo.uid, AppOpsManager.MODE_ALLOWED);
}
调用了AppOpsManager中的方法进行权限的授予
####添加特定APP的预置权限
通过上述代码阅读可以发现关键的代码在于grantDefaultPermissions,因为自己掌握着系统源码,所以对这段代码进行简单修改就可以
新增方法 grantPreGrantedPermissions(userId)
private void grantPreGrantedPermissions(userId){
String packageStrings = new String[]{
...permissions
}
for(String package:packageStrings){
PackageInfo pkg = getPackage(package);
Set permissions = new Set<Permission>();
for(Permission p:pkg.requestedPermissions){
permissions.add(p);
}
grantRuntimePermissions(pkg, permissions, permissionGrant.fixed,
permissionGrant.whitelisted, true /*whitelistRestrictedPermissions*/,
userId);
}
}
####一点心得
系统预装APP权限授予可能是系统开发中的一个基础操作,所以网上有较多资料,但是由于最近谷歌在对于权限控制有比较多的修改,从动态权限获取,到权限分组管理,到危险权限分组,到前台后台权限管理,可以发现每个大版本都会对权限这里做管理,所以不同版本的代码还是存在较大的不同,但是大体上的思路都是没有变化,即获取一个package,获取package的权限内容,授予权限三个部分,不过细节会有变化。细节包括权限组的权限授予,前台权限和后台权限,危险权限和一般权限,以及对之前已经安装apk的权限的兼容。