前言:像默认授予预制应用运行时权限的方法还是不好百度到的,用到的人不多,自己总结下最近的学习成果,方便后续使用。
参考资料:点击打开链接
代码分析:基于Android O
1. 运行时权限概念
Android 6.0 及更高版本中的 Android 应用权限模式旨在使权限更易于用户理解、更实用、更安全。该模式将需要危险权限(请参阅受影响的权限)的 Android 应用从安装时权限模式转移至运行时权限模式:
安装时权限(Android 5.1 及更低版本):用户在安装或更新应用时,向应用授予危险权限。OEM/运营商可以在不通知用户的情况下,预先安装具有预授权的应用。
运行时权限(Android 6.0 及更高版本):用户在应用运行时向应用授予危险权限。应用决定何时申请权限(例如,在应用启动或用户访问特定功能时申请权限),但必须允许用户授予/拒绝授予应用访问特定权限组的权限。OEM/运营商可以预安装应用,但不得预先授予权限(请参阅创建例外情况)。
运行时权限可以为用户提供应用正在寻求或已被授予的权限的额外上下文和可视性。运行时模式还鼓励开发者帮助用户了解应用请求权限的原因,并向用户透明展示授予或拒绝权限的好处和危害。
用户可使用“设置”中的“应用”菜单撤消应用权限。
PS:受影响的权限
Android 6.0 及更高版本要求危险权限必须使用运行时权限模式。危险权限是具有更高风险的权限(例如 READ_CALENDAR),此类权限允许寻求授权的应用访问用户私人数据或获取可对用户造成不利影响的设备控制权。要查看危险权限列表,请运行以下命令:
adb shell pm list permissions -g -d
Android 6.0 及更高版本不会更改常规权限的行为(包括常规权限、系统权限和签名权限在内的所有非危险权限)。常规权限是具有较低风险的权限(例如 SET_WALLPAPER),它允许请求授权的应用访问隔离的应用级功能,对其他应用、系统或用户的风险非常小。在 Android 5.1 及更低版本中,系统在安装应用时,自动向请求授权的应用授予常规权限,并且无需提示用户进行批准。
2. 默认授予预制应用运行时权限方法
通过使用 PackageManager 中的 DefaultPermissionGrantPolicy.java,您可以向核心操作系统功能的默认处理程序或提供程序进行预授权。
对应方法就是:
1.adb remount
2. 创建一个如下的xml放在/system/etc/default-permissions/下,比如叫做default_permissions.xml
<exceptions>
<!-- This is an example of an exception:
<exception
package="foo.bar.permission"
<permission name="android.permission.READ_CONTACTS" fixed="true"/>
<permission name="android.permission.READ_CALENDAR" fixed="false"/>
</exception>
</exceptions>
3.chmod a+r default_permissions.xml
4.恢复出厂设置查看对应应用的运行时权限是否默认打开
(以上方法仅供调试,aosp中就直接在mk文件中copy到对应目录下就好了)
授予预置权限后附加功能是设置中清除对应app的缓存数据后预置过的权限会默认恢复。
PS: fixed提供如下用途。
/**
* Permission flag: The permission is set in its current state
* because the app is a component that is a part of the system.
*
* @hide
*/
@SystemApi
public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4;
3. 代码依据
授予权限代码:
public void grantDefaultPermissions(int userId) {
if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
grantAllRuntimePermissions(userId);
} else {
grantPermissionsToSysComponentsAndPrivApps(userId);
grantDefaultSystemHandlerPermissions(userId);
grantDefaultPermissionExceptions(userId);
}
}
看下grantDefaultPermissionExceptions(userId);
private void grantDefaultPermissionExceptions(int userId) {
synchronized (mService.mPackages) {
mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
if (mGrantExceptions == null) {
mGrantExceptions = readDefaultPermissionExceptionsLPw();
}
// mGrantExceptions is null only before the first read and then
// it serves as a cache of the default grants that should be
// performed for every user. If there is an entry then the app
// is on the system image and supports runtime permissions.
Set<String> permissions = null;
final int exceptionCount = mGrantExceptions.size();
for (int i = 0; i < exceptionCount; i++) {
String packageName = mGrantExceptions.keyAt(i);
PackageParser.Package pkg = getSystemPackageLPr(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 (permissions == null) {
permissions = new ArraySet<>();
} else {
permissions.clear();
}
permissions.add(permissionGrant.name);
grantRuntimePermissionsLPw(pkg, permissions,
permissionGrant.fixed, userId);
}
}
}
}
继续看下readDefaultPermissionExceptionsLPw()这个方法:
private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
readDefaultPermissionExceptionsLPw() {
File[] files = getDefaultPermissionFiles();
if (files == null) {
return new ArrayMap<>(0);
}
ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
// Iterate over the files in the directory and scan .xml files
for (File file : files) {
if (!file.getPath().endsWith(".xml")) {
Slog.i(TAG, "Non-xml file " + file + " in " + file.getParent() + " directory, ignoring");
continue;
}
if (!file.canRead()) {
Slog.w(TAG, "Default permissions file " + file + " cannot be read");
continue;
}
try (
InputStream str = new BufferedInputStream(new FileInputStream(file))
) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(str, null);
parse(parser, grantExceptions);
} catch (XmlPullParserException | IOException e) {
Slog.w(TAG, "Error reading default permissions file " + file, e);
}
}
return grantExceptions;
}
接着看下getDefaultPermissionFiles(),这个方法是到/etc/下和vendor/etc/的default-permissions下读取对应xml文件
private File[] getDefaultPermissionFiles() {
ArrayList<File> ret = new ArrayList<File>();
File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
return ret.isEmpty() ? null : ret.toArray(new File[0]);
}
PS:/etc这个文件夹不是普通的创建出来的,而是链接到/system/etc下的,所以我们只要把default-permissions文件夹放到/system/etc下就好了。至于vendor这个我没搞过,可以先放在vendor/etc下试试。
4. 总结
本文主要讲述了如何默认授予默认预制应用运行时权限方法,但可能会有cts风险,使用需谨慎。OEM/运营商可以预安装应用,但不得预先授予权限(请参阅创建例外情况)。