blob: d90117905de69c5856ea2b2f980e40ef1430b511 [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 */
Svet Ganov8455ba22019-01-02 13:05:56 -080016package com.android.server.appop;
Peter Visontay859206c2018-01-03 15:43:19 +000017
Amith Yamasani23d4cd72019-04-10 17:57:00 -070018import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
19import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
Peter Visontay859206c2018-01-03 15:43:19 +000020import static android.app.AppOpsManager.MODE_ALLOWED;
21import static android.app.AppOpsManager.MODE_ERRORED;
Amith Yamasani23d4cd72019-04-10 17:57:00 -070022import static android.app.AppOpsManager.MODE_FOREGROUND;
Peter Visontay76712362018-04-19 22:04:14 +010023import static android.app.AppOpsManager.OP_COARSE_LOCATION;
Peter Visontay859206c2018-01-03 15:43:19 +000024import static android.app.AppOpsManager.OP_READ_SMS;
Peter Visontay76712362018-04-19 22:04:14 +010025import static android.app.AppOpsManager.OP_WIFI_SCAN;
Peter Visontay859206c2018-01-03 15:43:19 +000026import static android.app.AppOpsManager.OP_WRITE_SMS;
27
28import static com.google.common.truth.Truth.assertThat;
29import static com.google.common.truth.Truth.assertWithMessage;
30
Amith Yamasani23d4cd72019-04-10 17:57:00 -070031import android.app.ActivityManager;
Winson4e3b4352019-05-07 16:29:59 -070032import android.app.AppOpsManager;
Peter Visontay859206c2018-01-03 15:43:19 +000033import android.app.AppOpsManager.OpEntry;
34import android.app.AppOpsManager.PackageOps;
35import android.content.Context;
36import android.os.Handler;
37import android.os.HandlerThread;
38import android.os.Process;
Winson4e3b4352019-05-07 16:29:59 -070039import android.os.RemoteCallback;
Brett Chabota26eda92018-07-23 13:08:30 -070040
41import androidx.test.InstrumentationRegistry;
42import androidx.test.filters.SmallTest;
43import androidx.test.runner.AndroidJUnit4;
Peter Visontay859206c2018-01-03 15:43:19 +000044
45import org.junit.Before;
46import org.junit.Test;
47import org.junit.runner.RunWith;
48
49import java.io.File;
50import java.util.List;
Winson4e3b4352019-05-07 16:29:59 -070051import java.util.concurrent.CountDownLatch;
52import java.util.concurrent.TimeUnit;
53import java.util.concurrent.atomic.AtomicReference;
Peter Visontay859206c2018-01-03 15:43:19 +000054
55/**
56 * Unit tests for AppOpsService. Covers functionality that is difficult to test using CTS tests
57 * or for which we can write more detailed unit tests than CTS tests (because the internal APIs are
58 * more finegrained data than the public ones).
59 */
60@SmallTest
61@RunWith(AndroidJUnit4.class)
62public class AppOpsServiceTest {
63
64 private static final String TAG = AppOpsServiceTest.class.getSimpleName();
65 // State will be persisted into this XML file.
66 private static final String APP_OPS_FILENAME = "appops-service-test.xml";
67
68 private File mAppOpsFile;
69 private Context mContext;
70 private Handler mHandler;
71 private AppOpsService mAppOpsService;
72 private String mMyPackageName;
73 private int mMyUid;
74 private long mTestStartMillis;
75
76 @Before
77 public void setUp() {
78 mContext = InstrumentationRegistry.getTargetContext();
79 mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
80 if (mAppOpsFile.exists()) {
81 // Start with a clean state (persisted into XML).
82 mAppOpsFile.delete();
83 }
84
85 HandlerThread handlerThread = new HandlerThread(TAG);
86 handlerThread.start();
87 mHandler = new Handler(handlerThread.getLooper());
88 mMyPackageName = mContext.getOpPackageName();
89 mMyUid = Process.myUid();
90
91 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
92 mAppOpsService.mContext = mContext;
93 mTestStartMillis = System.currentTimeMillis();
94 }
95
96 @Test
97 public void testGetOpsForPackage_noOpsLogged() {
98 assertThat(getLoggedOps()).isNull();
99 }
100
101 @Test
102 public void testNoteOperationAndGetOpsForPackage() {
103 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
104 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
105
106 // Note an op that's allowed.
107 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
108 List<PackageOps> loggedOps = getLoggedOps();
109 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
110
111 // Note another op that's not allowed.
112 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
113 loggedOps = getLoggedOps();
114 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
115 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
116 }
117
Peter Visontay76712362018-04-19 22:04:14 +0100118 /**
119 * Tests the scenario where an operation's permission is controlled by another operation.
120 * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the
121 * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed.
122 */
123 @Test
124 public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
125 // This op controls WIFI_SCAN
126 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ALLOWED);
127
128 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
129 .isEqualTo(MODE_ALLOWED);
130
131 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
132 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
133
134 // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
135 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ERRORED);
136 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
137 .isEqualTo(MODE_ERRORED);
138
139 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
140 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
141 }
142
Peter Visontay859206c2018-01-03 15:43:19 +0000143 // Tests the dumping and restoring of the in-memory state to/from XML.
144 @Test
145 public void testStatePersistence() {
146 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
147 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
148 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
149 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
150 mAppOpsService.writeState();
151
152 // Create a new app ops service, and initialize its state from XML.
153 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
154 mAppOpsService.mContext = mContext;
155 mAppOpsService.readState();
156
157 // Query the state of the 2nd service.
158 List<PackageOps> loggedOps = getLoggedOps();
159 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
160 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
161 }
162
163 // Tests that ops are persisted during shutdown.
164 @Test
165 public void testShutdown() {
166 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
167 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
168 mAppOpsService.shutdown();
169
170 // Create a new app ops service, and initialize its state from XML.
171 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
172 mAppOpsService.mContext = mContext;
173 mAppOpsService.readState();
174
175 // Query the state of the 2nd service.
176 List<PackageOps> loggedOps = getLoggedOps();
177 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
178 }
179
180 @Test
181 public void testGetOpsForPackage() {
182 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
183 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
184
185 // Query all ops
186 List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
187 mMyUid, mMyPackageName, null /* all ops */);
188 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
189
190 // Query specific ops
191 loggedOps = mAppOpsService.getOpsForPackage(
192 mMyUid, mMyPackageName, new int[]{OP_READ_SMS, OP_WRITE_SMS});
193 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
194
195 // Query unknown UID
196 loggedOps = mAppOpsService.getOpsForPackage(mMyUid + 1, mMyPackageName, null /* all ops */);
197 assertThat(loggedOps).isNull();
198
199 // Query unknown package name
200 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, "fake.package", null /* all ops */);
201 assertThat(loggedOps).isNull();
202
203 // Query op code that's not been logged
204 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName,
205 new int[]{OP_WRITE_SMS});
206 assertThat(loggedOps).isNull();
207 }
208
209 @Test
210 public void testPackageRemoved() {
211 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
212 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
213
214 List<PackageOps> loggedOps = getLoggedOps();
215 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
216
217 mAppOpsService.packageRemoved(mMyUid, mMyPackageName);
218 assertThat(getLoggedOps()).isNull();
219 }
220
221 @Test
Winson4e3b4352019-05-07 16:29:59 -0700222 public void testPackageRemovedHistoricalOps() throws InterruptedException {
223 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
224 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
225
226 AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
227 historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, mMyPackageName,
228 AppOpsManager.UID_STATE_PERSISTENT, 0, 1);
229
230 mAppOpsService.addHistoricalOps(historicalOps);
231
232 AtomicReference<AppOpsManager.HistoricalOps> resultOpsRef = new AtomicReference<>();
233 AtomicReference<CountDownLatch> latchRef = new AtomicReference<>(new CountDownLatch(1));
234 RemoteCallback callback = new RemoteCallback(result -> {
235 resultOpsRef.set(result.getParcelable(AppOpsManager.KEY_HISTORICAL_OPS));
236 latchRef.get().countDown();
237 });
238
239 // First, do a fetch to ensure it's written
240 mAppOpsService.getHistoricalOps(mMyUid, mMyPackageName, null, 0, Long.MAX_VALUE, 0,
241 callback);
242
243 latchRef.get().await(5, TimeUnit.SECONDS);
244 assertThat(latchRef.get().getCount()).isEqualTo(0);
245 assertThat(resultOpsRef.get().isEmpty()).isFalse();
246
247 // Then, check it's deleted on removal
248 mAppOpsService.packageRemoved(mMyUid, mMyPackageName);
249
250 latchRef.set(new CountDownLatch(1));
251
252 mAppOpsService.getHistoricalOps(mMyUid, mMyPackageName, null, 0, Long.MAX_VALUE, 0,
253 callback);
254
255 latchRef.get().await(5, TimeUnit.SECONDS);
256 assertThat(latchRef.get().getCount()).isEqualTo(0);
257 assertThat(resultOpsRef.get().isEmpty()).isTrue();
258 }
259
260 @Test
Peter Visontay859206c2018-01-03 15:43:19 +0000261 public void testUidRemoved() {
262 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
263 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
264
265 List<PackageOps> loggedOps = getLoggedOps();
266 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
267
268 mAppOpsService.uidRemoved(mMyUid);
269 assertThat(getLoggedOps()).isNull();
270 }
271
Amith Yamasani23d4cd72019-04-10 17:57:00 -0700272 private void setupProcStateTests() {
273 // For the location proc state tests
274 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_FOREGROUND);
275 mAppOpsService.mConstants.FG_SERVICE_STATE_SETTLE_TIME = 0;
276 mAppOpsService.mConstants.TOP_STATE_SETTLE_TIME = 0;
277 mAppOpsService.mConstants.BG_STATE_SETTLE_TIME = 0;
278 }
279
280 @Test
281 public void testUidProcStateChange_cachedToTopToCached() throws Exception {
282 setupProcStateTests();
283
284 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
285 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
286 .isNotEqualTo(MODE_ALLOWED);
287
288 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP);
289 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
290 .isEqualTo(MODE_ALLOWED);
291
292 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
293 // Second time to make sure that settle time is overcome
294 Thread.sleep(50);
295 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
296 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
297 .isNotEqualTo(MODE_ALLOWED);
298 }
299
300 @Test
301 public void testUidProcStateChange_cachedToFgs() throws Exception {
302 setupProcStateTests();
303
304 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
305 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
306 .isNotEqualTo(MODE_ALLOWED);
307
308 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
309 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
310 .isNotEqualTo(MODE_ALLOWED);
311 }
312
313 @Test
314 public void testUidProcStateChange_cachedToFgsLocation() throws Exception {
315 setupProcStateTests();
316
317 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
318 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
319 .isNotEqualTo(MODE_ALLOWED);
320
321 mAppOpsService.updateUidProcState(mMyUid,
322 PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
323 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
324 .isEqualTo(MODE_ALLOWED);
325 }
326
327 @Test
328 public void testUidProcStateChange_topToFgs() throws Exception {
329 setupProcStateTests();
330
331 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
332 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
333 .isNotEqualTo(MODE_ALLOWED);
334
335 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP);
336 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
337 .isEqualTo(MODE_ALLOWED);
338
339 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
340 // Second time to make sure that settle time is overcome
341 Thread.sleep(50);
342 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
343 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
344 .isNotEqualTo(MODE_ALLOWED);
345 }
346
347 @Test
348 public void testUidProcStateChange_topToFgsLocationToFgs() throws Exception {
349 setupProcStateTests();
350
351 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
352 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
353 .isNotEqualTo(MODE_ALLOWED);
354
355 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP);
356 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
357 .isEqualTo(MODE_ALLOWED);
358
359 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
360 // Second time to make sure that settle time is overcome
361 Thread.sleep(50);
362 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
363 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
364 .isEqualTo(MODE_ALLOWED);
365
366 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
367 // Second time to make sure that settle time is overcome
368 Thread.sleep(50);
369 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
370 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
371 .isNotEqualTo(MODE_ALLOWED);
372 }
373
Peter Visontay859206c2018-01-03 15:43:19 +0000374 private List<PackageOps> getLoggedOps() {
375 return mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName, null /* all ops */);
376 }
377
378 private void assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis,
379 long minRejectMillis, int mode) {
380
381 boolean opLogged = false;
382 for (PackageOps pkgOps : loggedOps) {
383 assertWithMessage("Unexpected UID").that(mMyUid).isEqualTo(pkgOps.getUid());
384 assertWithMessage("Unexpected package name").that(mMyPackageName).isEqualTo(
385 pkgOps.getPackageName());
386
387 for (OpEntry opEntry : pkgOps.getOps()) {
388 if (opCode != opEntry.getOp()) {
389 continue;
390 }
391 opLogged = true;
392
393 assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode());
394 if (minMillis > 0) {
395 assertWithMessage("Unexpected timestamp")
396 .that(opEntry.getTime()).isAtLeast(minMillis);
397 }
398 if (minRejectMillis > 0) {
399 assertWithMessage("Unexpected rejection timestamp")
400 .that(opEntry.getRejectTime()).isAtLeast(minRejectMillis);
401 }
402 }
403 }
404 assertWithMessage("Op was not logged").that(opLogged).isTrue();
405 }
406}