[Android][Framework] 全方位理解Android权限之Android权限系统1
- 2020 年 1 月 20 日
- 筆記
系列目录请点击这里: 全方位理解Android权限
因为东忙西忙没时间整理这一块的东西,拖了有点久,现在继续更新
权限的性质
我们知道,Android应用都运行在沙盒中,默认情况下这些应用只能访问他们自己的域,即自己的文件和非常少量的系统服务。为了能够和系统或者其他应用交互,app就需要申请额外的一些权限。
permission(权限)实际上就是一个简单的字串,申明需要做哪些类型的操作。
比如文件读权限就是这么一个字串:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
Android系统预置了很多这样的权限,可以在官网查看文档:Manifest.permission。
权限的定义是在/frameworks/base/core/res/AndroidManifest.xml
在AOSP的生成目录内./out/target/common/R/android/Manifest.java
有对应定义(已删掉部分代码):
package android; public final class Manifest { public static final class permission { @android.annotation.SystemApi public static final String ACCESS_CACHE_FILESYSTEM="android.permission.ACCESS_CACHE_FILESYSTEM"; @android.annotation.SystemApi public static final String ACCESS_CHECKIN_PROPERTIES="android.permission.ACCESS_CHECKIN_PROPERTIES"; public static final String ACCESS_COARSE_LOCATION="android.permission.ACCESS_COARSE_LOCATION"; } public static final class permission_group { public static final String CALENDAR="android.permission-group.CALENDAR"; public static final String CAMERA="android.permission-group.CAMERA"; public static final String CONTACTS="android.permission-group.CONTACTS"; public static final String STORAGE="android.permission-group.STORAGE"; }
如果要看系统内已知的权限列表,可以使用pm list permissions
命令:
> adb shell pm list permissions All Permissions: permission:android.permission.REBOOT permission:android.permission.BIND_VPN_SERVICE permission:com.google.android.gallery3d.permission.GALLERY_PROVIDER permission:com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS ...
如果加上-f
选项,可以打印出定义权限的package、label、description和protection level。
> adb shell pm list permissions -f All Permissions: + permission:android.permission.REBOOT package:android label:null description:null protectionLevel:signature|privileged + permission:android.permission.BIND_VPN_SERVICE package:android label:null description:null protectionLevel:signature + permission:android.permission.GET_PACKAGE_SIZE package:android label:计算应用存储空间 description:允许应用检索其代码、数据和缓存大小 protectionLevel:normal + permission:android.permission.BROADCAST_STICKY package:android label:发送持久广播 description:允许该应用发送持久广播消息,此类消息在广播结束后仍会保留。过度使用可能会导致手机使用过多内存,从而降低其速度或稳定性。 protectionLevel:normal
一般,权限名的前缀是定义它的包名+.permission.
。因为内置的权限都定义在android
包内,所以系统权限命名都是android.permission.
开头。
比如上面的例子,REBOOT和BIND_VPN_SERVICE都是系统内部权限,GALLERY_PROVIDER是Gallery应用定义的,RECEIVE_LAUNCH_BROADCASTS是默认launcher定义的。
申请权限
一般权限是在AndroidManifest.xml文件中添加<uses-permission>
标签完成,关于AndroidManifest可以参考我前面写的AndroidManifest解析流程wossoneri.github.io。当然也可以使用<permission>
标签定义新的权限。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app" android:versionCode="1" android:versionName="1.0" > <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:name="SampleApp" ...> </application> </manifest>
权限管理
在应用安装的时候,PackageManagerService就对每个应用授予了权限。package manager管理了一个数据库,用来维护预置或者用户安装的package。维护的内容包括:安装路径,版本号,签名证书,每个package拿到的权限列表和一个在本设备上定义的所有权限列表。
其实pm list permissions命令就是通过package manager查询权限列表的
这个package数据库保存在/data/system/packages.xml路径下,每当有应用安装,更新或者卸载的时候都会更新这个xml文件。
看一下这个文件是怎么记录爱奇艺HD的:
<package name="com.qiyi.video.pad" codePath="/system/vendor/app/Aiqiyi" nativeLibraryPath="/system/vendor/app/Aiqiyi/lib" primaryCpuAbi="armeabi" publicFlags="945307205" privateFlags="0" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="80740" userId="10031" isOrphaned="true"> <sigs count="1"> <cert index="2" key="e700f576394b490f8c2f1708c6040c10a9f337af87" /> </sigs> <perms> <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" /> <item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" /> <item name="android.permission.SYSTEM_ALERT_WINDOW" granted="true" flags="0" /> <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" /> <item name="android.permission.EXPAND_STATUS_BAR" granted="true" flags="0" /> <item name="com.android.launcher.permission.UNINSTALL_SHORTCUT" granted="true" flags="0" /> <item name="android.permission.BLUETOOTH" granted="true" flags="0" /> <item name="android.permission.CHANGE_WIFI_MULTICAST_STATE" granted="true" flags="0" /> <item name="android.permission.GET_TASKS" granted="true" flags="0" /> <item name="android.permission.AUTHENTICATE_ACCOUNTS" granted="true" flags="0" /> <item name="android.permission.INTERNET" granted="true" flags="0" /> <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" /> <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" /> <item name="android.permission.DISABLE_KEYGUARD" granted="true" flags="0" /> <item name="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.qiyi.video.pad" granted="true" flags="0" /> <item name="com.qiyi.video.pad.permission.MIPUSH_RECEIVE" granted="true" flags="0" /> <item name="android.permission.VIBRATE" granted="true" flags="0" /> <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" /> <item name="android.permission.REQUEST_INSTALL_PACKAGES" granted="true" flags="0" /> <item name="com.android.launcher.permission.INSTALL_SHORTCUT" granted="true" flags="0" /> <item name="android.permission.WAKE_LOCK" granted="true" flags="0" /> </perms> <proper-signing-keyset identifier="9" /> </package>
这个xml文件还有很多譬如<shared-user>
这类标签,这些在后面分析userId的地方再看。现在看一下permission相关的内容。
每个package都是由一个<package>
标签包裹,里面包含了
- 分配的UID userId=”10031”
- 签名证书
<cert>
标签 - 分配的权限 在
<perm>
标签下
当用代码获取已安装的应用的信息时,得到的就是包含了<package>
标签下面所有内容的PackageInfo实例对象。
使用android.content.pm.PackageManager.getPackageInfo()通过代码获取
权限组
在权限定义文件里会定义权限组,然后在单独的权限中指定该权限属于哪个权限组。
<!-- Used for runtime permissions related to the shared external storage. --> <permission-group android:name="android.permission-group.STORAGE" android:icon="@drawable/perm_group_storage" android:label="@string/permgrouplab_storage" android:description="@string/permgroupdesc_storage" android:priority="900" /> <permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:permissionGroup="android.permission-group.STORAGE" android:label="@string/permlab_sdcardRead" android:description="@string/permdesc_sdcardRead" android:protectionLevel="dangerous" /> <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:permissionGroup="android.permission-group.STORAGE" android:label="@string/permlab_sdcardWrite" android:description="@string/permdesc_sdcardWrite" android:protectionLevel="dangerous" />
- 如果应用没有获得与当前申请的权限在同一权限组的其他权限的授权,那么系统将以这个权限组的描述信息去提示用户,而不是具体申请的权限的描述信息。比如,一个应用申请了READ_CONTACTS权限,系统会提示用户”应用需要访问设备的联系人(包含读写)”,如果用户同意授权,系统只会赋予应用之前申请的权限(在这里就只是READ_CONTACTS)。
- 如果应用已经获得了与正在申请的权限同一个权限组的其他权限的授权,那么系统会自动将正在申请的权限授予应用,不需要任何与用户的交互行为。比如,如果一个应用之前已经获得了READ_CONTACTS权限的授权,那么在之后应用请求WRITE_CONTACTS权限时,系统会自动将该权限授予应用。
权限保护等级ProtectionLevel
在查看权限定义时frameworks/base/core/res/AndroidManifest.xml
,可以看到有一个标签叫做protectionLevel
,下面放一个典型例子SYSTEM_ALERT_WINDOW的权限定义:
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW" android:label="@string/permlab_systemAlertWindow" android:description="@string/permdesc_systemAlertWindow" android:protectionLevel="signature|preinstalled|appop|pre23|development" />
protectionLevel
是定义权限时的一个重要属性,它表示一个权限的级别,在很大程度上它也决定了一个权限被授权的方式(由系统安装时自动授权或者由用户来决定是否授权)。 protectionLevel
可以分为两类:基础权限级别和附加权限级别。
基础权限级别
normal
这是个默认值,也就是没有指定protectionLevel
的权限默认获得此level。它表示这是一个对系统和其他应用低风险的权限。有该标记的权限是不需要用户确认就可以直接赋予应用程序的。
dangerous
较高风险的权限。该标记的权限一般涉及到访问用户的隐私数据或者其他一些控制设备的行为,可能会给用户带来影响。比如READ_SMS,读取短消息;比如CAMERA,允许使用摄像头。所以系统不会自动授权这类权限,而是会弹出对话框告诉用户,由用户进行选择是否授予权限。
signature
如果请求权限的app与声明权限的app签名一致,系统会自动赋予权限,而不会通知用户或者征求用户的同意。 否则需要通过intent将用户引导到权限管理界面由用户决定是否授权。
这属于最高级的权限等级,因为它需要有加密密钥的拥有权,而这个密钥只有这个app或者系统平台才会拥有。这也就意味着其他人无法随意使用这个权限。系统内置的signature权限一般都是由管理设备的系统App使用,也就是需要系统签名。
signatureOrSystem
相当于signature | privileged
这个权限等级有两种应用可以自动获取该类型权限的授权:
与定义这个权限的apk拥有相同的签名的应用(这一点和Signature类型的权限相同)。
在/system/priv-app目录下的应用(即拥有超级权限的系统应用)。
这可以让制造商的预置应用即使没有与该权限一致的签名也可以通过作为系统应用去使用该权限。
附加权限级别
除了基础权限级别的其他权限级别都属于附加权限级别。它们必须附加在基础权限级别上使用。从目前系统定义的权限来看,附加权限级别基本都是与signature基础权限级别搭配使用。
可以理解为附加权限级别是在为signature级别的权限开后门,使signature级别的权限在特定的条件下能够授权给特定类型的应用。
- privileged:只能与signature同时使用。signature | privileged与signatureOrSystem意义相同。
- system:与privileged相同,是privileged的老版本。
- development:development applications可以被自动授予此权限。
- appop:此类权限会与AppOpsManager来配合完成对应用操作的限制(AppOpsManager在后面的小节介绍)。
- pre23:应用请求此类权限后,系统将在应用安装时自动授权给那些targetSdkVersion在23(Android 6.0)以下的应用。
- installer:此类权限自动被授权给那些负责安装apk的系统app。
- verifier:此类权限自动被授权给那些负责验证apk的系统app。
- preinstalled:此类权限可以自动被授权给任何预安装在system image中的app,不只是privileged app。
- setup:此类权限自动被授予安装向导app。
可以通过dumpsys命令查看应用权限授予情况:
> dumpsys package com.xxxx.test requested permissions: android.permission.SYSTEM_ALERT_WINDOW Shared users: SharedUser [android.uid.system] (c5f2604): userId=1000 install permissions: android.permission.SYSTEM_ALERT_WINDOW: granted=true, flags=[ GRANTED_BY_DEFAULT ]
所以对于前面提到的SYSTEM_ALERT_WINDOW权限,因为其权限类别为:"signature|preinstalled|appop|pre23|development"
,所以,如果不是系统应用,也不是预置应用,那么就可以通过把app的targetSdkVersion调至23以下,来默认获得此权限。这里面还有一个appop权限,稍后会介绍这个东西。
默认授予权限
前面介绍protectionLevel
的时候知道,只要App满足对应权限的保护级别,就可以默认获取对应权限。但是有一个没有提到,就是dangerous的权限。
我们知道,dangerous权限是需要用户手动确认的,所以要怎样默认授予其对应权限呢?
这个涉及到Framework层的修改。Framework中pm目录下有一个专门负责应用权限授予的类:
frameworks/base/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
里面一个核心方法是grantRuntimePermissionsLPw
。这篇主要做介绍,源码分析下一篇再做。
private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions, boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) { if (pkg.requestedPermissions.isEmpty()) { return; } List<String> requestedPermissions = pkg.requestedPermissions; Set<String> grantablePermissions = null; // If this is the default Phone or SMS app we grant permissions regardless // whether the version on the system image declares the permission as used since // selecting the app as the default Phone or SMS the user makes a deliberate // choice to grant this app the permissions needed to function. For all other // apps, (default grants on first boot and user creation) we don't grant default // permissions if the version on the system image does not declare them. if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) { PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName); if (sysPs != null) { if (sysPs.pkg.requestedPermissions.isEmpty()) { return; } if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) { grantablePermissions = new ArraySet<>(requestedPermissions); requestedPermissions = sysPs.pkg.requestedPermissions; } } } final int grantablePermissionCount = requestedPermissions.size(); for (int i = 0; i < grantablePermissionCount; i++) { String permission = requestedPermissions.get(i); // If there is a disabled system app it may request a permission the updated // version ot the data partition doesn't, In this case skip the permission. if (grantablePermissions != null && !grantablePermissions.contains(permission)) { continue; } if (permissions.contains(permission)) { final int flags = mService.getPermissionFlags(permission, pkg.packageName, userId); // If any flags are set to the permission, then it is either set in // its current state by the system or device/profile owner or the user. // In all these cases we do not want to clobber the current state. // Unless the caller wants to override user choices. The override is // to make sure we can grant the needed permission to the default // sms and phone apps after the user chooses this in the UI. if (flags == 0 || isDefaultPhoneOrSms) { // Never clobber policy or system. final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED | PackageManager.FLAG_PERMISSION_POLICY_FIXED; if ((flags & fixedFlags) != 0) { continue; } mService.grantRuntimePermission(pkg.packageName, permission, userId); if (DEBUG) { Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ") + permission + " to default handler " + pkg.packageName); } int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; if (systemFixed) { newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; } mService.updatePermissionFlags(permission, pkg.packageName, newFlags, newFlags, userId); } // If a component gets a permission for being the default handler A // and also default handler B, we grant the weaker grant form. if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0 && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0 && !systemFixed) { if (DEBUG) { Log.i(TAG, "Granted not fixed " + permission + " to default handler " + pkg.packageName); } mService.updatePermissionFlags(permission, pkg.packageName, PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId); } } } }
AppOps
初次接触Appops是因为,现在国内很多App都要过多的权限,比如随便一个App都要访问手机号,不给它权限的话它就退出不让你使用。后来发现,可以通过Appops默默修改权限,比如第一次打开App的时候给它所有权限,然后再用Appops偷偷把权限禁用掉,这样App可以正常打开使用,但它也偷不到对应的信息了。
它的使用也很简单,一条命令的事情:
adb shell appops set com.tencent.gamehelper.smoba READ_EXTERNAL_STORAGE ignore
这样就禁掉了腾讯游戏对设备存储的访问。当然除了这种用法,对于一些特殊权限比如悬浮窗权限的授权也可以使用AppOps来解决。
AppOps 是什么
Appops是Application Operations的简称,是关于应用权限管理的一套方案,但这里的应用指的是系统应用,这些API不对第三方应用开放。Google从4.3开始推出Appops, 但一直到最新的Android N都没有在Settings里面开放Appops的入口,但这套方案却一直在后台默默的运行着。
上面的命令用到了ignore
模式,具体在AppOpsManager.java有4种模式定义:
// 允许执行相关权限 public static final int MODE_ALLOWED = 0; // 表示当前应用没有此权限,如果尝试使用该权限,就会静态地进入失败状态,出现应用莫名其妙crash。 public static final int MODE_IGNORED = 1; // 表示当前应用没有此权限,并且如果使用此权限会导致SecurityException public static final int MODE_ERRORED = 2; // 表示默认值,应该使用其默认的安全检查。这个模式并不常用,它应该和appop权限一起使用,并且调用者必须显式地检查和使用它 public static final int MODE_DEFAULT = 3;
AppOps工作流程
Appops工作框架如下

可以看到Appops的两个重要组成部分是AppOpsManager
和AppOpsService
,它们是典型的客户端和服务端设计,通过Binder跨进程调用。
AppOpsService
是做最终检查的系统服务,它的注册名字是appops,应用可以类似于
mAppOps=(AppOpsManager)getContext().getSystemService(Context.APP_OPS_SERVICE);
的方式来获取这个服务。
AppOpsManager
提供了接口,访问AppOpsService的核心方法。
AppOpsService
是在AMS构造函数中启动的:
mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler); mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null, new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName) { if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) { if (mAppOpsService.checkOperation(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { runInBackgroundDisabled(uid); } } } });
从启动来看,该服务创建了一个appops.xml
,这个文件最终位于/data/system/
目录下,用来存储各个app的权限设置和操作信息。
我们可以看一下这个文件,以高德导航为例:
<pkg n="com.autonavi.amapauto"> <uid n="10033" p="false"> <op n="0" t="1544544021632" pu="0" /> <op n="1" t="1544544021448" pu="0" /> <op n="11" m="1" /> <op n="23" r="1544544022096" /> <op n="24" m="2" r="1544544006269" /> <op n="51" t="1544547622239" pu="0" /> <op n="59" t="1544544022123" pu="0" /> <op n="60" t="1544544022123" pu="0" /> <op n="63" t="1544549408243" pu="0" /> </uid> </pkg>
然后设置悬浮窗权限
adb shell appops set com.autonavi.amapauto SYSTEM_ALERT_WINDOW allow
再次查看高德导航的权限
<pkg n="com.autonavi.amapauto"> <uid n="10033" p="false"> <op n="0" t="1544544021528" pu="0" /> <op n="1" t="1544544021371" pu="0" /> <op n="11" m="1" /> <op n="23" r="1544544022000" /> <op n="24" m="0" r="1544544006269" /> <----------发生了变化 <op n="51" t="1544544022020" pu="0" /> <op n="59" t="1544544022016" pu="0" /> <op n="60" t="1544544022016" pu="0" /> <op n="63" t="1544544362713" pu="0" /> </uid> </pkg>
从上面的定义来看,m="0"
指的是mode=MODE_ALLOWED=0
。
n=”24”指的是
public static final int OP_SYSTEM_ALERT_WINDOW = 24;
就是定义的悬浮窗权限了。
Api使用
AppOpsManager提供标准的API供APP调用,但google有明确说明,大部分只针对系统应用。但是想使用的话,可以尝试把Android源码里AppOpsManager.java打包一下,把jar包导入自己的工程,就可以使用了。
int checkOp(Stringop, int uid,StringpackageName)
Op对应一个权限操作,该接口来检测应用是否具有该项操作权限。
int noteOp(Stringop, int uid,StringpackageName)
和checkOp基本相同,但是在检验后会做记录。
int checkOpNoThrow(Stringop, int uid,StringpackageName)
和checkOp类似,但是权限错误,不会抛出SecurityException,而是返回AppOpsManager.MODE_ERRORED。
int noteOpNoThrow(Stringop, int uid,StringpackageName)
类似noteOp,但不会抛出SecurityException。
void setMode( int code, int uid, String packageName, int mode)
code代表具体的操作权限,mode代表要更改成的类型(允许/禁止/提示)
比较关键的就是这个setMode方法,比如通过代码设置悬浮窗权限的话,就需要这样:
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, getAppUid(packageGaode), packageGaode, AppOpsManager.MODE_ALLOWED); private int getAppUid(String packageName) { int uid = 0; try { PackageManager pm = mContext.getPackageManager(); ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); uid = ai.uid; } catch (NameNotFoundException e) { e.printStackTrace(); } return uid; }