blob: 36f84d0538171aa3ebccedb269971115c84f6dcb [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
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;
Brett Chabota26eda92018-07-23 13:08:30 -070034
35import androidx.test.InstrumentationRegistry;
36import androidx.test.filters.SmallTest;
37import androidx.test.runner.AndroidJUnit4;
Peter Visontay859206c2018-01-03 15:43:19 +000038
Svet Ganov8455ba22019-01-02 13:05:56 -080039import com.android.server.appop.AppOpsService;
Peter Visontay859206c2018-01-03 15:43:19 +000040import org.junit.Before;
41import org.junit.Test;
42import org.junit.runner.RunWith;
43
44import java.io.File;
45import java.util.List;
46
47/**
48 * Unit tests for AppOpsService. Covers functionality that is difficult to test using CTS tests
49 * or for which we can write more detailed unit tests than CTS tests (because the internal APIs are
50 * more finegrained data than the public ones).
51 */
52@SmallTest
53@RunWith(AndroidJUnit4.class)
54public class AppOpsServiceTest {
55
56 private static final String TAG = AppOpsServiceTest.class.getSimpleName();
57 // State will be persisted into this XML file.
58 private static final String APP_OPS_FILENAME = "appops-service-test.xml";
59
60 private File mAppOpsFile;
61 private Context mContext;
62 private Handler mHandler;
63 private AppOpsService mAppOpsService;
64 private String mMyPackageName;
65 private int mMyUid;
66 private long mTestStartMillis;
67
68 @Before
69 public void setUp() {
70 mContext = InstrumentationRegistry.getTargetContext();
71 mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
72 if (mAppOpsFile.exists()) {
73 // Start with a clean state (persisted into XML).
74 mAppOpsFile.delete();
75 }
76
77 HandlerThread handlerThread = new HandlerThread(TAG);
78 handlerThread.start();
79 mHandler = new Handler(handlerThread.getLooper());
80 mMyPackageName = mContext.getOpPackageName();
81 mMyUid = Process.myUid();
82
83 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
84 mAppOpsService.mContext = mContext;
85 mTestStartMillis = System.currentTimeMillis();
86 }
87
88 @Test
89 public void testGetOpsForPackage_noOpsLogged() {
90 assertThat(getLoggedOps()).isNull();
91 }
92
93 @Test
94 public void testNoteOperationAndGetOpsForPackage() {
95 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
96 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
97
98 // Note an op that's allowed.
99 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
100 List<PackageOps> loggedOps = getLoggedOps();
101 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
102
103 // Note another op that's not allowed.
104 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
105 loggedOps = getLoggedOps();
106 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
107 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
108 }
109
Peter Visontay76712362018-04-19 22:04:14 +0100110 /**
111 * Tests the scenario where an operation's permission is controlled by another operation.
112 * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the
113 * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed.
114 */
115 @Test
116 public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
117 // This op controls WIFI_SCAN
118 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ALLOWED);
119
120 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
121 .isEqualTo(MODE_ALLOWED);
122
123 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
124 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
125
126 // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
127 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ERRORED);
128 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
129 .isEqualTo(MODE_ERRORED);
130
131 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
132 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
133 }
134
Peter Visontay859206c2018-01-03 15:43:19 +0000135 // Tests the dumping and restoring of the in-memory state to/from XML.
136 @Test
137 public void testStatePersistence() {
138 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
139 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
140 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
141 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
142 mAppOpsService.writeState();
143
144 // Create a new app ops service, and initialize its state from XML.
145 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
146 mAppOpsService.mContext = mContext;
147 mAppOpsService.readState();
148
149 // Query the state of the 2nd service.
150 List<PackageOps> loggedOps = getLoggedOps();
151 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
152 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
153 }
154
155 // Tests that ops are persisted during shutdown.
156 @Test
157 public void testShutdown() {
158 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
159 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
160 mAppOpsService.shutdown();
161
162 // Create a new app ops service, and initialize its state from XML.
163 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
164 mAppOpsService.mContext = mContext;
165 mAppOpsService.readState();
166
167 // Query the state of the 2nd service.
168 List<PackageOps> loggedOps = getLoggedOps();
169 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
170 }
171
172 @Test
173 public void testGetOpsForPackage() {
174 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
175 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
176
177 // Query all ops
178 List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
179 mMyUid, mMyPackageName, null /* all ops */);
180 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
181
182 // Query specific ops
183 loggedOps = mAppOpsService.getOpsForPackage(
184 mMyUid, mMyPackageName, new int[]{OP_READ_SMS, OP_WRITE_SMS});
185 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
186
187 // Query unknown UID
188 loggedOps = mAppOpsService.getOpsForPackage(mMyUid + 1, mMyPackageName, null /* all ops */);
189 assertThat(loggedOps).isNull();
190
191 // Query unknown package name
192 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, "fake.package", null /* all ops */);
193 assertThat(loggedOps).isNull();
194
195 // Query op code that's not been logged
196 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName,
197 new int[]{OP_WRITE_SMS});
198 assertThat(loggedOps).isNull();
199 }
200
201 @Test
202 public void testPackageRemoved() {
203 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
204 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
205
206 List<PackageOps> loggedOps = getLoggedOps();
207 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
208
209 mAppOpsService.packageRemoved(mMyUid, mMyPackageName);
210 assertThat(getLoggedOps()).isNull();
211 }
212
213 @Test
214 public void testUidRemoved() {
215 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
216 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
217
218 List<PackageOps> loggedOps = getLoggedOps();
219 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
220
221 mAppOpsService.uidRemoved(mMyUid);
222 assertThat(getLoggedOps()).isNull();
223 }
224
225 private List<PackageOps> getLoggedOps() {
226 return mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName, null /* all ops */);
227 }
228
229 private void assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis,
230 long minRejectMillis, int mode) {
231
232 boolean opLogged = false;
233 for (PackageOps pkgOps : loggedOps) {
234 assertWithMessage("Unexpected UID").that(mMyUid).isEqualTo(pkgOps.getUid());
235 assertWithMessage("Unexpected package name").that(mMyPackageName).isEqualTo(
236 pkgOps.getPackageName());
237
238 for (OpEntry opEntry : pkgOps.getOps()) {
239 if (opCode != opEntry.getOp()) {
240 continue;
241 }
242 opLogged = true;
243
244 assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode());
245 if (minMillis > 0) {
246 assertWithMessage("Unexpected timestamp")
247 .that(opEntry.getTime()).isAtLeast(minMillis);
248 }
249 if (minRejectMillis > 0) {
250 assertWithMessage("Unexpected rejection timestamp")
251 .that(opEntry.getRejectTime()).isAtLeast(minRejectMillis);
252 }
253 }
254 }
255 assertWithMessage("Op was not logged").that(opLogged).isTrue();
256 }
257}