安卓权限预置设置

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

####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的权限的兼容。

相关推荐

  1. 权限预置设置

    2024-04-09 14:42:02       13 阅读
  2. Delphi v11 权限申请

    2024-04-09 14:42:02       28 阅读
  3. 手机APP开发___设置闹钟

    2024-04-09 14:42:02       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-09 14:42:02       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-09 14:42:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-09 14:42:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-09 14:42:02       20 阅读

热门阅读

  1. LeetCode热题Hot100 - 有效的括号

    2024-04-09 14:42:02       12 阅读
  2. 2024.4.8作业

    2024-04-09 14:42:02       12 阅读
  3. 3.10 Python数据类型转换

    2024-04-09 14:42:02       8 阅读
  4. c#有dll源码,整合到自己的exe中

    2024-04-09 14:42:02       12 阅读
  5. SQL Server 数据类型

    2024-04-09 14:42:02       13 阅读
  6. 目标 url 存在 host 头攻击漏洞

    2024-04-09 14:42:02       16 阅读
  7. 软件测试与QA的区别

    2024-04-09 14:42:02       13 阅读
  8. 题目:学习使用按位与 & 。

    2024-04-09 14:42:02       11 阅读
  9. MYSQL 5.7重置root密码

    2024-04-09 14:42:02       9 阅读
  10. idea 使用springboot helper 创建springboot项目

    2024-04-09 14:42:02       11 阅读
  11. git lfs如何使用

    2024-04-09 14:42:02       11 阅读
  12. 云数据库AWS Aurora(一)

    2024-04-09 14:42:02       10 阅读
  13. 关于ros中的回旋函数

    2024-04-09 14:42:02       12 阅读
  14. git知识

    git知识

    2024-04-09 14:42:02      9 阅读