blob: 45e2865e8e5aacd0aace4387236d7c3e1d24ef16 [file] [log] [blame]
Peter Visontay859206c2018-01-03 15:43:19 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.server;
17
18import static android.app.AppOpsManager.MODE_ALLOWED;
19import static android.app.AppOpsManager.MODE_ERRORED;
Peter Visontay76712362018-04-19 22:04:14 +010020import static android.app.AppOpsManager.OP_COARSE_LOCATION;
Peter Visontay859206c2018-01-03 15:43:19 +000021import static android.app.AppOpsManager.OP_READ_SMS;
Peter Visontay76712362018-04-19 22:04:14 +010022import static android.app.AppOpsManager.OP_WIFI_SCAN;
Peter Visontay859206c2018-01-03 15:43:19 +000023import static android.app.AppOpsManager.OP_WRITE_SMS;
24
25import static com.google.common.truth.Truth.assertThat;
26import static com.google.common.truth.Truth.assertWithMessage;
27
28import android.app.AppOpsManager.OpEntry;
29import android.app.AppOpsManager.PackageOps;
30import android.content.Context;
31import android.os.Handler;
32import android.os.HandlerThread;
33import android.os.Process;
34import android.support.test.InstrumentationRegistry;
35import android.support.test.filters.SmallTest;
36import android.support.test.runner.AndroidJUnit4;
37
38import org.junit.Before;
39import org.junit.Test;
40import org.junit.runner.RunWith;
41
42import java.io.File;
43import java.util.List;
44
45/**
46 * Unit tests for AppOpsService. Covers functionality that is difficult to test using CTS tests
47 * or for which we can write more detailed unit tests than CTS tests (because the internal APIs are
48 * more finegrained data than the public ones).
49 */
50@SmallTest
51@RunWith(AndroidJUnit4.class)
52public class AppOpsServiceTest {
53
54 private static final String TAG = AppOpsServiceTest.class.getSimpleName();
55 // State will be persisted into this XML file.
56 private static final String APP_OPS_FILENAME = "appops-service-test.xml";
57
58 private File mAppOpsFile;
59 private Context mContext;
60 private Handler mHandler;
61 private AppOpsService mAppOpsService;
62 private String mMyPackageName;
63 private int mMyUid;
64 private long mTestStartMillis;
65
66 @Before
67 public void setUp() {
68 mContext = InstrumentationRegistry.getTargetContext();
69 mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
70 if (mAppOpsFile.exists()) {
71 // Start with a clean state (persisted into XML).
72 mAppOpsFile.delete();
73 }
74
75 HandlerThread handlerThread = new HandlerThread(TAG);
76 handlerThread.start();
77 mHandler = new Handler(handlerThread.getLooper());
78 mMyPackageName = mContext.getOpPackageName();
79 mMyUid = Process.myUid();
80
81 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
82 mAppOpsService.mContext = mContext;
83 mTestStartMillis = System.currentTimeMillis();
84 }
85
86 @Test
87 public void testGetOpsForPackage_noOpsLogged() {
88 assertThat(getLoggedOps()).isNull();
89 }
90
91 @Test
92 public void testNoteOperationAndGetOpsForPackage() {
93 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
94 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
95
96 // Note an op that's allowed.
97 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
98 List<PackageOps> loggedOps = getLoggedOps();
99 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
100
101 // Note another op that's not allowed.
102 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
103 loggedOps = getLoggedOps();
104 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
105 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
106 }
107
Peter Visontay76712362018-04-19 22:04:14 +0100108 /**
109 * Tests the scenario where an operation's permission is controlled by another operation.
110 * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the
111 * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed.
112 */
113 @Test
114 public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
115 // This op controls WIFI_SCAN
116 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ALLOWED);
117
118 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
119 .isEqualTo(MODE_ALLOWED);
120
121 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
122 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
123
124 // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
125 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ERRORED);
126 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
127 .isEqualTo(MODE_ERRORED);
128
129 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
130 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
131 }
132
Peter Visontay859206c2018-01-03 15:43:19 +0000133 // Tests the dumping and restoring of the in-memory state to/from XML.
134 @Test
135 public void testStatePersistence() {
136 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
137 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
138 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
139 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
140 mAppOpsService.writeState();
141
142 // Create a new app ops service, and initialize its state from XML.
143 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
144 mAppOpsService.mContext = mContext;
145 mAppOpsService.readState();
146
147 // Query the state of the 2nd service.
148 List<PackageOps> loggedOps = getLoggedOps();
149 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
150 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
151 }
152
153 // Tests that ops are persisted during shutdown.
154 @Test
155 public void testShutdown() {
156 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
157 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
158 mAppOpsService.shutdown();
159
160 // Create a new app ops service, and initialize its state from XML.
161 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
162 mAppOpsService.mContext = mContext;
163 mAppOpsService.readState();
164
165 // Query the state of the 2nd service.
166 List<PackageOps> loggedOps = getLoggedOps();
167 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
168 }
169
170 @Test
171 public void testGetOpsForPackage() {
172 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
173 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
174
175 // Query all ops
176 List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
177 mMyUid, mMyPackageName, null /* all ops */);
178 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
179
180 // Query specific ops
181 loggedOps = mAppOpsService.getOpsForPackage(
182 mMyUid, mMyPackageName, new int[]{OP_READ_SMS, OP_WRITE_SMS});
183 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
184
185 // Query unknown UID
186 loggedOps = mAppOpsService.getOpsForPackage(mMyUid + 1, mMyPackageName, null /* all ops */);
187 assertThat(loggedOps).isNull();
188
189 // Query unknown package name
190 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, "fake.package", null /* all ops */);
191 assertThat(loggedOps).isNull();
192
193 // Query op code that's not been logged
194 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName,
195 new int[]{OP_WRITE_SMS});
196 assertThat(loggedOps).isNull();
197 }
198
199 @Test
200 public void testPackageRemoved() {
201 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
202 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
203
204 List<PackageOps> loggedOps = getLoggedOps();
205 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
206
207 mAppOpsService.packageRemoved(mMyUid, mMyPackageName);
208 assertThat(getLoggedOps()).isNull();
209 }
210
211 @Test
212 public void testUidRemoved() {
213 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
214 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
215
216 List<PackageOps> loggedOps = getLoggedOps();
217 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
218
219 mAppOpsService.uidRemoved(mMyUid);
220 assertThat(getLoggedOps()).isNull();
221 }
222
223 private List<PackageOps> getLoggedOps() {
224 return mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName, null /* all ops */);
225 }
226
227 private void assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis,
228 long minRejectMillis, int mode) {
229
230 boolean opLogged = false;
231 for (PackageOps pkgOps : loggedOps) {
232 assertWithMessage("Unexpected UID").that(mMyUid).isEqualTo(pkgOps.getUid());
233 assertWithMessage("Unexpected package name").that(mMyPackageName).isEqualTo(
234 pkgOps.getPackageName());
235
236 for (OpEntry opEntry : pkgOps.getOps()) {
237 if (opCode != opEntry.getOp()) {
238 continue;
239 }
240 opLogged = true;
241
242 assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode());
243 if (minMillis > 0) {
244 assertWithMessage("Unexpected timestamp")
245 .that(opEntry.getTime()).isAtLeast(minMillis);
246 }
247 if (minRejectMillis > 0) {
248 assertWithMessage("Unexpected rejection timestamp")
249 .that(opEntry.getRejectTime()).isAtLeast(minRejectMillis);
250 }
251 }
252 }
253 assertWithMessage("Op was not logged").that(opLogged).isTrue();
254 }
255}