本文基于Android 9.0源码分析
1.概述
在前面《Android 权限管理--03:后台定位权限源码分析》这篇文章中介绍到了AppOps机制的一个重要功能是track,举的具体例子是和电量耗电相关的定位权限,限制应用退到后台之后访问位置。这篇文章继续展开讲AppOps的access control。
/**
* App-ops are used for two purposes: Access control and tracking.
*
* <p>App-ops cover a wide variety of functionality from helping with runtime permissions access
* control and tracking to battery consumption tracking.
2.WRITE_SETTINGS权限
首先,我们看下WRITE_SETTINGS这个权限,平时如果三方应用需要申请此权限的时候需要如下操作:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(MainActivity.this)) {
Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
//启动指定Action的权限管理应用的弹窗Activity
startActivity(intent);
} else {
//如有该权限可以直接设置屏幕亮度
setLight();
}
}
权限管理4-1.PNG
然后就会出现如上弹窗选择,这里的权限申请流程和之前的runtime运行时权限不一样,走的是Appops检查机制。首先看下这个权限在framework中的定义,可以看到是signature|appop级别的。
-
signature级别:表示有系统platform系统签名的应用如果声明会默认授权;
-
appops级别:表示可以通过appops机制授权也就是上面的弹窗授权方式。
// framework/base/core/res/AndroidManifest.xml
<permission android:name="android.permission.WRITE_SETTINGS"
android:label="@string/permlab_writeSettings"
android:description="@string/permdesc_writeSettings"
android:protectionLevel="signature|preinstalled|appop|pre23" />
这里从Settings.System.canWrite()往下看源码是如何做检查的?这里往下会走到isCallingPackageAllowedToPerformAppOpsProtectedOperation()。
// frameworks/base/core/java/android/provider/Settings.java
public static boolean isCallingPackageAllowedToPerformAppOpsProtectedOperation(Context context,int uid, String callingPackage, boolean throwException, int appOpsOpCode, String[]
permissions, boolean makeNote) {
if (callingPackage == null) {
return false;
}
//1.获取AppOpsManager
AppOpsManager appOpsMgr = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
int mode = AppOpsManager.MODE_DEFAULT;
//2.检查AppOpsManager.OP_WRITE_SETTINGS权限授权值
if (makeNote) {
mode = appOpsMgr.noteOpNoThrow(appOpsOpCode, uid, callingPackage);
} else {
mode = appOpsMgr.checkOpNoThrow(appOpsOpCode, uid, callingPackage);
}
//3.根据授权值返回结果
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
return true;
case AppOpsManager.MODE_DEFAULT:
for (String permission : permissions) {
if (context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
return true;
}
}
default:
// this is for all other cases trickled down here...
if (!throwException) {
return false;
}
}
整体流程如下:这里补充下,检查mode值时,这里WRITE_SETTINGS权限在AppOps中的默认值是MODE_DEFAULT;此时再进一步通过context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED检查,如果是系统platform签名应用signature权限会默认授权,返回true,如果是三方应用则返回false。
权限管理4-2.png3.Appops实现
3.1 权限状态值
// frameworks/base/core/java/android/app/AppOpsManager.java
//允许
public static final int MODE_ALLOWED = 0;
//拒绝
public static final int MODE_IGNORED = 1;
//拒绝并抛出异常
public static final int MODE_ERRORED = 2;
//default模式,会进一步check权限,如上面的"修改系统设置"权限
public static final int MODE_DEFAULT = 3;
//前台模式,主要是后台位置权限使用,可以结合第三篇权限文章食用,有例子用到
public static final int MODE_FOREGROUND = 4;
3.2 权限定义值
这里关于appops权限定义的官方介绍是每个runtime permission权限值都会有对应的app op值对应,如下就是罗列了各个权限op对应的值。
<h3>Runtime permissions and app-ops</h3>
<p>Each platform defined runtime permission (beside background modifiers) has an associated app
op which is used for tracking but also to allow for silent failures.
public static final int OP_COARSE_LOCATION = 0;
/** @hide Access to fine location information. */
@UnsupportedAppUsage
public static final int OP_FINE_LOCATION = 1;
/** @hide Causing GPS to run. */
@UnsupportedAppUsage
public static final int OP_GPS = 2;
/** @hide */
@UnsupportedAppUsage
public static final int OP_VIBRATE = 3;
/** @hide */
@UnsupportedAppUsage
public static final int OP_READ_CONTACTS = 4;
/** @hide */
@UnsupportedAppUsage
public static final int OP_WRITE_CONTACTS = 5;
...
3.3 数据结构
AppOpsService.java中通过SparseArray<UidState> mUidStates来维护状态,key值为uid,value值为对应的UidState,相关类基本结构如下:
权限管理4-3.png对应的持久化文件位置是在/data/system/appops.xml 文件内容如下:
-
uid标签:用于记录和 uid 相关的限制信息;
-
pkg标签:用于记录和具体应用的限制信息;
-
pkg标签下的uid标签的n:表示pkg所属的uid;
-
pkg标签下的uid标签的p:表示是否是privileged应用;
-
n:表示权限值;
-
m:表示权限的授权值;
-
st:标签下的是对应的权限的拒绝时间,允许时间,持续使用时间等。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<app-ops>
<uid n="10210">
<op n="0" m="1" />
<op n="87" m="0" />
</uid>
...
<pkg n="com.android.recentspsp">
<uid n="1000" p="true">
<op n="3">
<st n="214748364801" t="1606363097865" d="50" pu="0" />
</op>
</uid>
</pkg>
...
通过如下adb命令可以看到相应的信息:
adb shell dumpsys appops
如这里的COARSE_LOCATION在 2020-11-23 11:24:50.018时检查权限是允许的:
权限管理4-4.png关于AppOpsManager中也有一些重要的变量和方法整理如下:这里面有一个sOpDefaultMode数组,表示每个权限op都有对应的默认授权mode值。
权限管理4-5.png3.4 Appops api
appops调用主要是通过AppOpsManager提供的api方法进行操作:
权限管理4-6.png(1)检查权限:AppOpsManager.checkOp()最终会调用到AppOpsService.checkOperationUnchecked()
private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,boolean raw) {
...
//1\. 获取对应权限的op值
code = AppOpsManager.opToSwitch(code);
//2\. 获取uid对应的UidState
UidState uidState = getUidStateLocked(uid, false);
//3\. 如果在uid标签下有对应的值则直接返回,android10这里的raw为false(主要增加前后台判断)
if (uidState != null && uidState.opModes != null
&& uidState.opModes.indexOfKey(code) >= 0) {
final int rawMode = uidState.opModes.get(code);
return raw ? rawMode : uidState.evalMode(code, rawMode);
}
//4\. 如果没有从uid标签下没有取到值,则会从pkg标签下取pkg对应的Op
Op op = getOpLocked(code, uid, packageName, false, false);
//5 如果也取不到的话,则直接返回该权限的默认状态值
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
}
return raw ? op.mode : op.evalMode();
}
}
(2)检查权限并记录:AppOpsManager.noteOp()最终会调用到AppOpsService.noteOperationUnchecked()
final Ops ops = getOpsRawLocked(uid, packageName, isPrivileged, true /* edit */);
...
final Op op = getOpLocked(ops, code, true);
...
final UidState uidState = ops.uidState;
...
final int switchCode = AppOpsManager.opToSwitch(code);
...
//1\. 如果uid标签下有对应的值则读取
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
//1.1 如果拒绝的话,则直接返回,并且更新拒绝时间
if (uidMode != AppOpsManager.MODE_ALLOWED) {
...
op.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
uidState.state, flags);
mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
return uidMode;
}
} else {
//2\. uid标签下无的话从pkg标签下读取
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
final int mode = switchOp.evalMode();
//2.2 如果拒绝的话,同样直接返回,并更新拒绝时间
if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
...
op.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
uidState.state, flags);
mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
return mode;
}
}
//3\. 走到这里则为允许,并且更新允许时间
op.accessed(System.currentTimeMillis(), proxyUid, proxyPackageName,
uidState.state, flags);
mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName,
AppOpsManager.MODE_ALLOWED);
return AppOpsManager.MODE_ALLOWED;
}
}
(3)设置权限值:AppOpsService.setUidMode()
public void setUidMode(int code, int uid, int mode) {
final int defaultMode = AppOpsManager.opToDefaultMode(code);
UidState uidState = getUidStateLocked(uid, false);
if (uidState == null) {
if (mode == defaultMode) {
return;
}
uidState = new UidState(uid);
uidState.opModes = new SparseIntArray();
uidState.opModes.put(code, mode);
mUidStates.put(uid, uidState);
scheduleWriteLocked();
} else if (uidState.opModes == null) {
if (mode != defaultMode) {
uidState.opModes = new SparseIntArray();
uidState.opModes.put(code, mode);
scheduleWriteLocked();
}
} else {
if (uidState.opModes.indexOfKey(code) >= 0 && uidState.opModes.get(code) == mode) {
return;
}
if (mode == defaultMode) {
uidState.opModes.delete(code);
if (uidState.opModes.size() <= 0) {
uidState.opModes = null;
}
} else {
uidState.opModes.put(code, mode);
}
scheduleWriteLocked();
}
uidState.evalForegroundOps(mOpModeWatchers);
(4)监听权限变化: 如以下是监听悬浮窗权限状态变化;悬浮窗权限类似如上的修改系统设置权限,声明的级别也是appops权限,这里添加的监听表示如果开关了权限,需要更新app状态,有可能app悬浮窗正在使用。
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
AppOpsManager.OnOpChangedInternalListener opListener =
new AppOpsManager.OnOpChangedInternalListener() {
@Override public void onOpChanged(int op, String packageName) {
updateAppOpsState();
}
};
mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener);
<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" />
网友评论