1.App-ops作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 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.
*
* <h2>Access control</h2>
*
* <p>App-ops can either be controlled for each uid or for each package. Which one is used depends
* on the API provider maintaining this app-op. For any security or privacy related app-op the
* provider needs to control the app-op for per uid as all security and privacy is based on uid in
* Android.
*
* <p>To control access the app-op can be set to a mode to:
* <dl>
* <dt>{@link #MODE_DEFAULT}
* <dd>Default behavior, might differ from app-op or app-op
* <dt>{@link #MODE_ALLOWED}
* <dd>Allow the access
* <dt>{@link #MODE_IGNORED}
* <dd>Don't allow the access, i.e. don't perform the requested action or return no or dummy
* data
* <dt>{@link #MODE_ERRORED}
* <dd>Throw a {@link SecurityException} on access. This can be suppressed by using a
* {@code ...noThrow} method to check the mode
* </dl>
*/

从AppOpsManager注解可以看出,App-Ops主要用于权限控制和追述一些重要事件。

App-Ops可以对每一个uid或者每一个应用程序进行操作权限控制,此接入控制可以被设置成一下四种模式:

  • MODE_DEFAULT:默认行为,用于区分不同app-op
  • MODE_ALLOWED: 运行操作
  • MODE_IGNORED: 不允许操作。不执行请求操作,或者返回空或者虚拟数据
  • MODE_ERRORED:此操作发生之后,抛SecurityException异常。

每个平台会给每一个运行时权限定义一个app-op,用于追述和运行异常操作。如果运行时权限被拒绝,系统将抛SecurityException异常,但是如果权限被授予并且app op是MODE_IGNORED,那么调用者将获得虚拟数据。

如果被平台定义的App-ops被覆盖,app-op权限将被设置成MODE_DEFAULT,如果希望将app-ops设置成MODE_ALLOWED或者MODE_IGNORED,都需要校验之后再赋值。

2.AppopsService分析

2.1 AppopsService启动和数据结构分析

AppOpsService的启动是再ActivityManagerService中启动的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// data/system/appops.xml文件
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);
}
}
}
});

public AppOpsService(File storagePath, Handler handler) {
mFile = new AtomicFile(storagePath);
mHandler = handler;
readState();
}

在AppOpsService初始化的时候,通过readState()去读取data/system/appops.xml文件把他保存在内存中。

appops.xml文件大致如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<app-ops v="1">

<pkg n="com.ts.appservice.settings">
<uid n="1000" p="false">
<op n="59" tp="1622129003446" pu="0" />
<op n="60" tp="1622129003446" pu="0" />
</uid>

</pkg>
<pkg n="com.ts.appservice.mediacenterservice">
<uid n="1000" p="false">
<op n="59" tp="1622128997408" pu="0" />
<op n="60" tp="1622128997408" pu="0" />
</uid>
</pkg>

<pkg n="com.iflytek.autofly.systemserver">
<uid n="1000" p="false">
<op n="0" />
<op n="2" tp="1577845829783" d="182727" />
<op n="41" tp="1622129006431" d="9182935" />
<op n="42" tp="1622129006431" d="9182935" />
<op n="59" tp="1622129006246" pu="0" />
<op n="60" tp="1622129006246" pu="0" />
<op n="76" tp="1577837011354" d="9001156" />
</uid>
</pkg>
......
</app-ops>

最外层用app-ops标签,里面包裹了,再里面包裹了,最里面包裹各式各样不同的操作。每个uid可能存在于多个包中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
void readState() {
synchronized (mFile) {
synchronized (this) {
//1 打开文件
FileInputStream stream;
try {
stream = mFile.openRead();
} catch (FileNotFoundException e) {
Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
return;
}
boolean success = false;
mUidStates.clear();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
;
}

if (type != XmlPullParser.START_TAG) {
throw new IllegalStateException("no start tag found");
}

int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}

String tagName = parser.getName();
if (tagName.equals("pkg")) {
//2 解析package数据
readPackage(parser);
} else if (tagName.equals("uid")) {
//3解析uid数据
readUidOps(parser);
} else {
Slog.w(TAG, "Unknown element under <app-ops>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
success = true;
} catch (IllegalStateException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (NullPointerException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (NumberFormatException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (XmlPullParserException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (IOException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (IndexOutOfBoundsException e) {
Slog.w(TAG, "Failed parsing " + e);
} finally {
if (!success) {
mUidStates.clear();
}
try {
stream.close();
} catch (IOException e) {
}
}
}
}
}

该方法中主要关注readPackage(parser)readUidOps(parser)函数,先看看readUidOps(parser)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void readUidOps(XmlPullParser parser) throws NumberFormatException,
XmlPullParserException, IOException {
//1 读取n节点,也就是name 代表uid
final int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
int outerDepth = parser.getDepth();
int type;
//2 下面读取uid下面的节点
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}

String tagName = parser.getName();
if (tagName.equals("op")) {
//3 解析op节点
final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
// 创建UidState并且放入mUidStates集合中
UidState uidState = getUidStateLocked(uid, true);
if (uidState.opModes == null) {
uidState.opModes = new SparseIntArray();
}
uidState.opModes.put(code, mode);
} else {
//4 未知节点直接跳过
Slog.w(TAG, "Unknown element under <uid-ops>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}

这里主要是创建一个UidState。然后创建opModes,之后将code和mode关系保存在opModes中。

然后再分析一下readPackage(parser)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void readPackage(XmlPullParser parser) throws NumberFormatException,
XmlPullParserException, IOException {
//1 n代表name ,也就是包名
String pkgName = parser.getAttributeValue(null, "n");
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}

String tagName = parser.getName();
if (tagName.equals("uid")) {
//2 解析uid相关数据,这里是uid在pkg内的情况
readUid(parser, pkgName);
} else {
Slog.w(TAG, "Unknown element under <pkg>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}

从上面看解释pkg只是解析package name,然后解析uid的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
XmlPullParserException, IOException {
//1 n代表uid
int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
//2 p代表是否预装在/system/priv-app/下
String isPrivilegedString = parser.getAttributeValue(null, "p");
boolean isPrivileged = false;
//3 如果xml里面没有写这个pkg是否是priv-app就使用PMS查询,最终确定isPrivileged变量
if (isPrivilegedString == null) {
try {
IPackageManager packageManager = ActivityThread.getPackageManager();
if (packageManager != null) {
ApplicationInfo appInfo = ActivityThread.getPackageManager()
.getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid));
if (appInfo != null) {
isPrivileged = (appInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
} else {
// Could not load data, don't add to cache so it will be loaded later.
return;
}
} catch (RemoteException e) {
Slog.w(TAG, "Could not contact PackageManager", e);
}
} else {
isPrivileged = Boolean.parseBoolean(isPrivilegedString);
}
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
//4解析op标签
String tagName = parser.getName();
if (tagName.equals("op")) {
//5 创建op数据结构,这里可以看到op包含的数据结构有uid,包名,name,我们在xml中看到op的name全都是数字,从Integer.parseInt来看也是这么实现的
Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
String mode = parser.getAttributeValue(null, "m");
//6 设置op的mode
if (mode != null) {
op.mode = Integer.parseInt(mode);
}
//7 设置时间
String time = parser.getAttributeValue(null, "t");
if (time != null) {
op.time = Long.parseLong(time);
}
//8 获取拒绝时间
time = parser.getAttributeValue(null, "r");
if (time != null) {
op.rejectTime = Long.parseLong(time);
}
//9 获取间隔
String dur = parser.getAttributeValue(null, "d");
if (dur != null) {
op.duration = Integer.parseInt(dur);
}
//10代理uid
String proxyUid = parser.getAttributeValue(null, "pu");
if (proxyUid != null) {
op.proxyUid = Integer.parseInt(proxyUid);
}
//11 代理报名
String proxyPackageName = parser.getAttributeValue(null, "pp");
if (proxyPackageName != null) {
op.proxyPackageName = proxyPackageName;
}
//12 这里和readUidOps函数是一样的,创建UidState和pkgOps集合
UidState uidState = getUidStateLocked(uid, true);
if (uidState.pkgOps == null) {
uidState.pkgOps = new ArrayMap<>();
}
//13 创建Ops数据结构,添加到uidState
Ops ops = uidState.pkgOps.get(pkgName);
if (ops == null) {
ops = new Ops(pkgName, uidState, isPrivileged);
uidState.pkgOps.put(pkgName, ops);
}
//14添加Op到ops中
ops.put(op.op, op);
} else {
Slog.w(TAG, "Unknown element under <pkg>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}

这里我们可以看出Ops数据结构分为三层:

1 SparseArray mUidStates 用于存放uid和对应的uid下的状态,用UidState变量代表
2 UidState,一个uid对应一个UidState,然而一个uid可以对应多个package, 每个package下面都有一个Ops代表一组操作
3 每个Ops下又有多个op
另外每个UidState下还有一组opModes,分别保存code和mode的对应管理。

到这里就分析完成了解析,和数据结构的组织。 我们来线下AppOps如何检查权限。

2.2 Appops权限校验

我们以AppOpsManager类的checkOp和checkOpNoThrow为例子说明AppOps的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public int checkOp(String op, int uid, String packageName) {
return checkOp(strOpToOp(op), uid, packageName);
}

public int checkOp(int op, int uid, String packageName) {
try {
int mode = mService.checkOperation(op, uid, packageName);
if (mode == MODE_ERRORED) {
throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

public int checkOpNoThrow(String op, int uid, String packageName) {
return checkOpNoThrow(strOpToOp(op), uid, packageName);
}

public int checkOpNoThrow(int op, int uid, String packageName) {
try {
return mService.checkOperation(op, uid, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

两个方法的区别在于,如果mode是MODE_ERRORED抛不抛异常。

当我们看到strOpToOp(op)似乎就可以猜到是把一个字符串转为从一个整形,就是转化成op code,似乎瞬间明白op标签的n是数字的原因了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static int strOpToOp(String op) {
Integer val = sOpStrToOp.get(op);
if (val == null) {
throw new IllegalArgumentException("Unknown operation string: " + op);
}
return val;
}

for (int i=0; i<_NUM_OP; i++) {
if (sOpToString[i] != null) {
sOpStrToOp.put(sOpToString[i], i);
}
}

private static String[] sOpToString = new String[] {
OPSTR_COARSE_LOCATION,
OPSTR_FINE_LOCATION,
null,
null,
......
}

在AppOpsManager中有op的名字对应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/** @hide No operation specified. */
public static final int OP_NONE = -1;
/** @hide Access to coarse location information. */
public static final int OP_COARSE_LOCATION = 0;
/** @hide Access to fine location information. */
public static final int OP_FINE_LOCATION = 1;
/** @hide Causing GPS to run. */
public static final int OP_GPS = 2;
/** @hide */
public static final int OP_VIBRATE = 3;
/** @hide */
public static final int OP_READ_CONTACTS = 4;
/** @hide */
public static final int OP_WRITE_CONTACTS = 5;
/** @hide */
public static final int OP_READ_CALL_LOG = 6;
/** @hide */
public static final int OP_WRITE_CALL_LOG = 7;
/** @hide */
public static final int OP_READ_CALENDAR = 8;
/** @hide */
public static final int OP_WRITE_CALENDAR = 9;
/** @hide */
public static final int OP_WIFI_SCAN = 10;
/** @hide */
public static final int OP_POST_NOTIFICATION = 11;
/** @hide */
public static final int OP_NEIGHBORING_CELLS = 12;
/** @hide */
public static final int OP_CALL_PHONE = 13;
.......

sOpStrToOp是使用sOpToString所维护的字符串作为key,数组下标作为value去维护关系,所以这里的code就是数组下表,看来要加AppOps还要注意和google的兼容性。其实都是使用AppOpsService的checkOperation函数来检查的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Override
public int checkOperation(int code, int uid, String packageName) {
//1 检查uid是否具有UPDATE_APP_OPS_STATS权限
verifyIncomingUid(uid);
//2 检查code是否合理
verifyIncomingOp(code);
//3 根据uid和包名获取包名
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
synchronized (this) {
//4 检查user多用户相关的权限
if (isOpRestrictedLocked(uid, code, resolvedPackageName)) {
return AppOpsManager.MODE_IGNORED;
}
//5 根据code转换为op code,这里又进行了一个转换的操作
code = AppOpsManager.opToSwitch(code);
//6 获取UidState
UidState uidState = getUidStateLocked(uid, false);
if (uidState != null && uidState.opModes != null) {
//7 检查opModes下维护的code权限
final int uidMode = uidState.opModes.get(code);
if (uidMode != AppOpsManager.MODE_ALLOWED) {
return uidMode;
}
}
//8 获取pkg下的Op
Op op = getOpLocked(code, uid, resolvedPackageName, false);
if (op == null) {
//9 op不存在使用默认规则
return AppOpsManager.opToDefaultMode(code);
}
//9op 存在返回op下的mode
return op.mode;
}
}

public static int opToSwitch(int op) {
return sOpToSwitch[op];
}

private static int[] sOpToSwitch = new int[] {
OP_COARSE_LOCATION,
OP_COARSE_LOCATION,
OP_COARSE_LOCATION,
OP_VIBRATE,
OP_READ_CONTACTS,
OP_WRITE_CONTACTS,
OP_READ_CALL_LOG,
OP_WRITE_CALL_LOG,
OP_READ_CALENDAR,
OP_WRITE_CALENDAR,
     ....
     }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// android.os.Process.java
// UID定义
/**
* Defines the root UID.
* @hide
*/
public static final int ROOT_UID = 0;

/**
* Defines the UID/GID under which system code runs.
*/
public static final int SYSTEM_UID = 1000;

/**
* Defines the UID/GID under which the telephony code runs.
*/
public static final int PHONE_UID = 1001;

/**
* Defines the UID/GID for the user shell.
* @hide
*/
public static final int SHELL_UID = 2000;

/**
* Defines the UID/GID for the log group.
* @hide
*/
public static final int LOG_UID = 1007;

/**
* Defines the UID/GID for the WIFI supplicant process.
* @hide
*/
public static final int WIFI_UID = 1010;
......
/**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
* to applications.
*/
public static final int FIRST_APPLICATION_UID = 10000;

/**
* Last of application-specific UIDs starting at
* {@link #FIRST_APPLICATION_UID}.
*/
public static final int LAST_APPLICATION_UID = 19999;

/**
* First uid used for fully isolated sandboxed processes (with no permissions of their own)
* @hide
*/
public static final int FIRST_ISOLATED_UID = 99000;

/**
* Last uid used for fully isolated sandboxed processes (with no permissions of their own)
* @hide
*/
public static final int LAST_ISOLATED_UID = 99999;

函数很简单,使用注释中的九个步骤去判断op code对应的授权模式。 
1,2,3 三个步骤都是检查参数和调用者的合理性,这里需要说明的是如果resolvedPackageName不存在的时候返回AppOpsManager.MODE_IGNORED,看来这就是一种op对应的mode,这里对mode做一下说明

//1 操作允许
public static final int MODE_ALLOWED = 0;
//操作不允许,但是checkOp不会抛出安全异常,表示忽略
public static final int MODE_IGNORED = 1;
//3 表示操作不允许,checkOp操作会抛出安全异常
public static final int MODE_ERRORED = 2;
//4 默认行为,可能进一步检查权限
public static final int MODE_DEFAULT = 3;

步骤4检查多用户相关的授权,我们先放一下再来分析
步骤5根据code转换为op code,这里又进行了一个转换的操作,对于这里的转换,注释里面说的比较清楚,就是多个code可能对应同一个op code,所以这里要进行一次转换。大多数时候code都是和opcode相同的。 
步骤7 首先检查uid下的opModes,我们在分析读取appops.xml文件的时候,有时候一个uid标签是不在pkg内部的,这种uid标签下申明的mode,优先级要高于pkg下的op,所以在这里先去判断uid的op code 对应的mode.
步骤8 则是当uid下的规则不存在,继而检查pkg下的op
步骤9是如果没有指定规则,则采用默认规则,opToDefaultMode函数也是使用查表的方式确定默认规则,表则AppOpsManager类的sOpDefaultMode变量中维护。

2.3 AppOpsManager下一个方法
1
public int checkPackage(int uid, String packageName) 

这个方法会在uid下创建对应的uidState和一个Ops返回给用户,创建成功返回说明uid和packagename是对应的上的,返回AppOpsManager.MODE_ALLOWED,否则返回AppOpsManager.MODE_ERRORED

1
public int noteOp(int op, int uid, String packageName)

noteOp 函数比checkOp函数额外多出的功能就是会创建对应的op,另外会更新一些op操作的时间用于统计信息

1
public int startOp(int op, int uid, String packageName) 

表示一个长时间运行的操作,比noteOp增加了一个统计信息,放在一个叫starting的集合里可以dump到,调用finishOp结束操作。

2.4 多用户检测

AppOps的另外一个功能是针对多用户的授权检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, String[] exceptionPackages) {
//1 不是针对自己进程的首先规则,要检查MANAGE_APP_OPS_RESTRICTIONS权限
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
//2 跨用户的调用检查INTERACT_ACROSS_USERS_FULL 权限
if (userHandle != UserHandle.getCallingUserId()) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission
.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
&& mContext.checkCallingOrSelfPermission(Manifest.permission
.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+ " INTERACT_ACROSS_USERS to interact cross user ");
}
}
//3 检查code合理性
verifyIncomingOp(code);
Preconditions.checkNotNull(token);
setUserRestrictionNoCheck(code, restricted, token, userHandle, exceptionPackages);
}

做了一系列检查后进入setUserRestrictionNoCheck真正的创先相应的受限规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
int userHandle, String[] exceptionPackages) {
boolean notifyChange = false;
//1 获取token所对应的ClientRestrictionState,这代表一个用户应用进程,用户应用进程会发布一些针对某些user的限制
synchronized (AppOpsService.this) {
ClientRestrictionState restrictionState = mOpUserRestrictions.get(token);

if (restrictionState == null) {
try {
restrictionState = new ClientRestrictionState(token);
} catch (RemoteException e) {
return;
}
//2 不存在的情况创建,放入mOpUserRestrictions集合
mOpUserRestrictions.put(token, restrictionState);
}
//3给userHandle添加一个受限规则,其中code为op,restricted为是否收到限制,真为是,exceptionPackages
//是指白名单的包,这些包属于userHandle这个用户。
if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) {
notifyChange = true;
}
//4 没有任何授权需要创建,直接销毁
if (restrictionState.isDefault()) {
mOpUserRestrictions.remove(token);
restrictionState.destroy();
}
}

if (notifyChange) {
//5 通知监听者
notifyWatchersOfChange(code);
}
}

public boolean setRestriction(int code, boolean restricted,
String[] excludedPackages, int userId) {
boolean changed = false;
//1 创建user数据结构,维护每个user的授权情况
if (perUserRestrictions == null && restricted) {
perUserRestrictions = new SparseArray<>();
}

if (perUserRestrictions != null) {
boolean[] userRestrictions = perUserRestrictions.get(userId);
if (userRestrictions == null && restricted) {
//2创建每种权限的受限情况
userRestrictions = new boolean[AppOpsManager._NUM_OP];
perUserRestrictions.put(userId, userRestrictions);
}
//3 如果设置完成后,对所有op的操作都是不受限制的,则清除该限制的数据结构
if (userRestrictions != null && userRestrictions[code] != restricted) {
userRestrictions[code] = restricted;
if (!restricted && isDefault(userRestrictions)) {
perUserRestrictions.remove(userId);
userRestrictions = null;
}
changed = true;
}
//4 创建excludedPackages相关信息
if (userRestrictions != null) {
final boolean noExcludedPackages = ArrayUtils.isEmpty(excludedPackages);
if (perUserExcludedPackages == null && !noExcludedPackages) {
perUserExcludedPackages = new SparseArray<>();
}
if (perUserExcludedPackages != null && !Arrays.equals(excludedPackages,
perUserExcludedPackages.get(userId))) {
if (noExcludedPackages) {
perUserExcludedPackages.remove(userId);
if (perUserExcludedPackages.size() <= 0) {
perUserExcludedPackages = null;
}
} else {
perUserExcludedPackages.put(userId, excludedPackages);
}
changed = true;
}
}
}

return changed;
}

这个api的作用就是限制一个user去做某件事情。

2.4 限制用户操作例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public class OverlayTouchActivity extends Activity {
private final IBinder mToken = new Binder();

@Override
protected void onResume() {
super.onResume();
setOverlayAllowed(false);
}

@Override
protected void onPause() {
super.onPause();
setOverlayAllowed(true);
}

private void setOverlayAllowed(boolean allowed) {
AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
if (appOpsManager != null) {
appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
!allowed, mToken);
appOpsManager.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW,
!allowed, mToken);
}
}
}

在进入OverlayTouchActivity这个页面时候就不允许这个用户弹出window,除非弹框来自Privileged的包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private boolean isOpRestrictedLocked(int uid, int code, String packageName) {
int userHandle = UserHandle.getUserId(uid);
final int restrictionSetCount = mOpUserRestrictions.size();

for (int i = 0; i < restrictionSetCount; i++) {
// For each client, check that the given op is not restricted, or that the given
// package is exempt from the restriction.
ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
if (restrictionState.hasRestriction(code, packageName, userHandle)) {
// opAllowSystemBypassRestriction检查一些op code是否对Privileged开放
if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
// If we are the system, bypass user restrictions for certain codes
synchronized (this) {
Ops ops = getOpsRawLocked(uid, packageName, true);
if ((ops != null) && ops.isPrivileged) {
return false;
}
}
}
return true;
}
}
return false;
}