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 APKsfinal 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退到后台,仍然需要定位权限,则需要授予后台定位权限,这部分就是保证首先授予定位权限,之后授予后台定位权限
// 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// permissionif (DEBUG) {Log.i(TAG, "Granting OP_" + AppOpsManager.opToName(appOp)+ " to " + pkg.packageName);}mContext.getSystemService(AppOpsManager.class).setUidMode(appOp, pkg.applicationInfo.uid, AppOpsManager.MODE_ALLOWED);}
添加特定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的权限的兼容。
