因为最近要在系统里做权限预置的功能,就进行了了解

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中声明的接口,代码如下

  1. /**
  2. * Provider for package names.
  3. */
  4. public interface PackagesProvider {
  5. /**
  6. * Gets the packages for a given user.
  7. * @param userId The user id.
  8. * @return The package names.
  9. */
  10. public String[] getPackages(int userId);
  11. }

用于设置不同场景的包名,之后在系统默认应用权限授予的时候会用到。 接下来就是这个类的核心代码了

  1. public void grantDefaultPermissions(int userId) {
  2. grantPermissionsToSysComponentsAndPrivApps(userId);
  3. grantDefaultSystemHandlerPermissions(userId);
  4. grantDefaultPermissionExceptions(userId);
  5. synchronized (mLock) {
  6. mDefaultPermissionsGrantedUsers.put(userId, userId);
  7. }
  8. }

可以很清楚的知道,在grantDefaultPermissions中,按照不同的应用类型,分为系统组件和私有APP,处理系统消息的APP,额外处理的权限应用,分别按照用户id对权限进行权限授予 ####系统APP的权限授予 首先查看grantPermissionsToSysComponentsAndPrivApps的功能,代码如下:

  1. private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
  2. Log.i(TAG, "Granting permissions to platform components for user " + userId);
  3. List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser(
  4. DEFAULT_PACKAGE_INFO_QUERY_FLAGS, UserHandle.USER_SYSTEM);
  5. for (PackageInfo pkg : packages) {
  6. if (pkg == null) {
  7. continue;
  8. }
  9. if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
  10. || !doesPackageSupportRuntimePermissions(pkg)
  11. || ArrayUtils.isEmpty(pkg.requestedPermissions)) {
  12. continue;
  13. }
  14. grantRuntimePermissionsForSystemPackage(userId, pkg);
  15. }
  16. }

逻辑也很简单,使用

  1. List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser(
  2. 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)方法,提供给一些包括预装应用或是后门应用 代码如下

  1. private void grantDefaultPermissionExceptions(int userId) {
  2. synchronized (mLock) {
  3. if (mGrantExceptions == null) {
  4. mGrantExceptions = readDefaultPermissionExceptionsLocked();
  5. }
  6. }
  7. Set<String> permissions = null;
  8. final int exceptionCount = mGrantExceptions.size();
  9. for (int i = 0; i < exceptionCount; i++) {
  10. String packageName = mGrantExceptions.keyAt(i);
  11. PackageInfo pkg = getSystemPackageInfo(packageName);
  12. List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
  13. final int permissionGrantCount = permissionGrants.size();
  14. for (int j = 0; j < permissionGrantCount; j++) {
  15. DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
  16. if (!isPermissionDangerous(permissionGrant.name)) {
  17. continue;
  18. }
  19. if (permissions == null) {
  20. permissions = new ArraySet<>();
  21. } else {
  22. permissions.clear();
  23. }
  24. permissions.add(permissionGrant.name);
  25. grantRuntimePermissions(pkg, permissions, permissionGrant.fixed,
  26. permissionGrant.whitelisted, true /*whitelistRestrictedPermissions*/,
  27. userId);
  28. }
  29. }
  30. }

可看出也是同样的逻辑,获取包名,获取权限,之后授予权限,可以看到这里有一个新的方法是isPermissionDangerous(permissionGrant.name),这个方法也是在9.0之后新增的,在安卓9.0,进一步将权限分为危险权限和非危险权限,比如网络权限就是非危险权限,会默认授予,其他权限会在运行时弹窗 在grantDefaultPermissionExceptions方法中需要授予权限的包集合为mGrantExceptions,通过readDefaultPermissionExceptionsLocked()获取权限。

  1. private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
  2. readDefaultPermissionExceptionsLocked() {
  3. File[] files = getDefaultPermissionFiles;
  4. if (files == null) {
  5. return new ArrayMap<>(0);
  6. }
  7. for (File file : files) {
  8. ...
  9. try (
  10. InputStream str = new BufferedInputStream(new FileInputStream(file))
  11. ) {
  12. XmlPullParser parser = Xml.newPullParser();
  13. parser.setInput(str, null);
  14. parse(parser, grantExceptions);
  15. } catch (XmlPullParserException | IOException e) {
  16. }
  17. }
  18. return grantExceptions;
  19. }
  1. private File[] getDefaultPermissionFiles() {
  2. ArrayList<File> ret = new ArrayList<File>();
  3. File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
  4. Collections.addAll(ret, dir.listFiles());
  5. dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
  6. Collections.addAll(ret, dir.listFiles());
  7. dir = new File(Environment.getOdmDirectory(), "etc/default-permissions");
  8. Collections.addAll(ret, dir.listFiles());
  9. dir = new File(Environment.getProductDirectory(), "etc/default-permissions");
  10. Collections.addAll(ret, dir.listFiles());
  11. dir = new File(Environment.getSystemExtDirectory(), "etc/default-permissions");
  12. Collections.addAll(ret, dir.listFiles());
  13. if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
  14. dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
  15. if (dir.isDirectory() && dir.canRead()) {
  16. Collections.addAll(ret, dir.listFiles());
  17. }
  18. }
  19. return ret.isEmpty() ? null : ret.toArray(new File[0]);
  20. }

即从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中

  1. // Automatically attempt to grant split permissions to older APKs
  2. final List<PermissionManager.SplitPermissionInfo> splitPermissions =
  3. mContext.getSystemService(PermissionManager.class).getSplitPermissions();
  4. final int numSplitPerms = splitPermissions.size();
  5. for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
  6. final PermissionManager.SplitPermissionInfo splitPerm =
  7. splitPermissions.get(splitPermNum);
  8. if (applicationInfo != null
  9. && applicationInfo.targetSdkVersion < splitPerm.getTargetSdk()
  10. && permissionsWithoutSplits.contains(splitPerm.getSplitPermission())) {
  11. permissions.addAll(splitPerm.getNewPermissions());
  12. }
  13. }

在最新的安卓版本中,对后台权限进行了控制,比如定位权限,如果APP退到后台,仍然需要定位权限,则需要授予后台定位权限,这部分就是保证首先授予定位权限,之后授予后台定位权限

  1. // Sort requested permissions so that all permissions that are a foreground permission (i.e.
  2. // permissions that have a background permission) are before their background permissions.
  3. final String[] sortedRequestedPermissions = new String[numRequestedPermissions];
  4. int numForeground = 0;
  5. int numOther = 0;
  6. for (int i = 0; i < numRequestedPermissions; i++) {
  7. String permission = requestedPermissions[i];
  8. if (getBackgroundPermission(permission) != null) {
  9. sortedRequestedPermissions[numForeground] = permission;
  10. numForeground++;
  11. } else {
  12. sortedRequestedPermissions[numRequestedPermissions - 1 - numOther] =
  13. permission;
  14. numOther++;
  15. }
  16. }

还有一种极端情况需要考虑,即APP升级的情况,一个APP升级前已经授予了一些权限,升级时新增了一些权限的申请,那在升级之后就有两部分权限需要处理,一部分是升级前已经授予了的权限,一部分是新授予的权限,考虑到之前的前台权限和后台权限,需要进行额外的处理。

  1. if (pm.checkPermission(fgPerm, pkg.packageName)
  2. == PackageManager.PERMISSION_GRANTED) {
  3. // Upgrade the app-op state of the fg permission to allow bg access
  4. // TODO: Dont' call app ops from package manager code.
  5. mContext.getSystemService(AppOpsManager.class).setUidMode(
  6. AppOpsManager.permissionToOp(fgPerm), uid,
  7. AppOpsManager.MODE_ALLOWED);
  8. break;
  9. }

最后就是权限的授予了,

  1. int appOp = AppOpsManager.permissionToOpCode(permission);
  2. if (appOp != AppOpsManager.OP_NONE
  3. && AppOpsManager.opToDefaultMode(appOp)
  4. != AppOpsManager.MODE_ALLOWED) {
  5. // Permission has a corresponding appop which is not allowed by default
  6. // We must allow it as well, as it's usually checked alongside the
  7. // permission
  8. if (DEBUG) {
  9. Log.i(TAG, "Granting OP_" + AppOpsManager.opToName(appOp)
  10. + " to " + pkg.packageName);
  11. }
  12. mContext.getSystemService(AppOpsManager.class).setUidMode(
  13. appOp, pkg.applicationInfo.uid, AppOpsManager.MODE_ALLOWED);
  14. }

调用了AppOpsManager中的方法进行权限的授予

添加特定APP的预置权限

通过上述代码阅读可以发现关键的代码在于grantDefaultPermissions,因为自己掌握着系统源码,所以对这段代码进行简单修改就可以 新增方法 grantPreGrantedPermissions(userId)

  1. private void grantPreGrantedPermissions(userId){
  2. String packageStrings = new String[]{
  3. ...permissions
  4. }
  5. for(String package:packageStrings){
  6. PackageInfo pkg = getPackage(package);
  7. Set permissions = new Set<Permission>();
  8. for(Permission p:pkg.requestedPermissions){
  9. permissions.add(p);
  10. }
  11. grantRuntimePermissions(pkg, permissions, permissionGrant.fixed,
  12. permissionGrant.whitelisted, true /*whitelistRestrictedPermissions*/,
  13. userId);
  14. }
  15. }

一点心得

系统预装APP权限授予可能是系统开发中的一个基础操作,所以网上有较多资料,但是由于最近谷歌在对于权限控制有比较多的修改,从动态权限获取,到权限分组管理,到危险权限分组,到前台后台权限管理,可以发现每个大版本都会对权限这里做管理,所以不同版本的代码还是存在较大的不同,但是大体上的思路都是没有变化,即获取一个package,获取package的权限内容,授予权限三个部分,不过细节会有变化。细节包括权限组的权限授予,前台权限和后台权限,危险权限和一般权限,以及对之前已经安装apk的权限的兼容。