1 /* 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 */ 16 package com.android.server.appop; 17 18 import static android.app.AppOpsManager.MODE_ALLOWED; 19 import static android.app.AppOpsManager.MODE_ERRORED; 20 import static android.app.AppOpsManager.OP_COARSE_LOCATION; 21 import static android.app.AppOpsManager.OP_FLAGS_ALL; 22 import static android.app.AppOpsManager.OP_FLAG_SELF; 23 import static android.app.AppOpsManager.OP_READ_SMS; 24 import static android.app.AppOpsManager.OP_WIFI_SCAN; 25 import static android.app.AppOpsManager.OP_WRITE_SMS; 26 import static android.os.UserHandle.getUserId; 27 28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; 33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 34 35 import static com.google.common.truth.Truth.assertThat; 36 import static com.google.common.truth.Truth.assertWithMessage; 37 38 import static org.junit.Assert.assertNotEquals; 39 import static org.junit.Assert.assertNotNull; 40 import static org.mockito.ArgumentMatchers.any; 41 import static org.mockito.ArgumentMatchers.anyInt; 42 import static org.mockito.ArgumentMatchers.anyString; 43 import static org.mockito.ArgumentMatchers.eq; 44 import static org.mockito.ArgumentMatchers.nullable; 45 46 import android.app.AppOpsManager; 47 import android.app.AppOpsManager.OpEntry; 48 import android.app.AppOpsManager.PackageOps; 49 import android.content.ContentResolver; 50 import android.content.Context; 51 import android.content.pm.PackageManagerInternal; 52 import android.os.Handler; 53 import android.os.HandlerThread; 54 import android.os.Process; 55 import android.provider.Settings; 56 import android.util.ArrayMap; 57 58 import androidx.test.InstrumentationRegistry; 59 import androidx.test.filters.SmallTest; 60 import androidx.test.runner.AndroidJUnit4; 61 62 import com.android.dx.mockito.inline.extended.StaticMockitoSession; 63 import com.android.server.LocalManagerRegistry; 64 import com.android.server.LocalServices; 65 import com.android.server.pm.PackageManagerLocal; 66 import com.android.server.pm.UserManagerInternal; 67 import com.android.server.pm.pkg.AndroidPackage; 68 import com.android.server.pm.pkg.PackageState; 69 import com.android.server.pm.pkg.PackageStateInternal; 70 71 import org.junit.After; 72 import org.junit.Before; 73 import org.junit.Test; 74 import org.junit.runner.RunWith; 75 import org.mockito.quality.Strictness; 76 77 import java.io.File; 78 import java.util.Collections; 79 import java.util.List; 80 import java.util.Map; 81 82 /** 83 * Unit tests for AppOpsService. Covers functionality that is difficult to test using CTS tests 84 * or for which we can write more detailed unit tests than CTS tests (because the internal APIs are 85 * more finegrained data than the public ones). 86 */ 87 @SmallTest 88 @RunWith(AndroidJUnit4.class) 89 public class AppOpsServiceTest { 90 91 private static final String TAG = AppOpsServiceTest.class.getSimpleName(); 92 // State will be persisted into this XML file. 93 private static final String APP_OPS_FILENAME = "appops.test.xml"; 94 private static final String APP_OPS_ACCESSES_FILENAME = "appops_accesses.test.xml"; 95 96 private static final Context sContext = InstrumentationRegistry.getTargetContext(); 97 private static final String sMyPackageName = sContext.getOpPackageName(); 98 99 private File mStorageFile; 100 private File mRecentAccessesFile; 101 private Handler mHandler; 102 private AppOpsService mAppOpsService; 103 private int mMyUid; 104 private long mTestStartMillis; 105 private StaticMockitoSession mMockingSession; 106 setupAppOpsService()107 private void setupAppOpsService() { 108 mAppOpsService = new AppOpsService(mRecentAccessesFile, mStorageFile, mHandler, 109 spy(sContext)); 110 mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver()); 111 112 // Always approve all permission checks 113 doNothing().when(mAppOpsService.mContext).enforcePermission(anyString(), anyInt(), 114 anyInt(), nullable(String.class)); 115 } 116 117 @Before setUp()118 public void setUp() { 119 mStorageFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME); 120 mRecentAccessesFile = new File(sContext.getFilesDir(), APP_OPS_ACCESSES_FILENAME); 121 mStorageFile.delete(); 122 mRecentAccessesFile.delete(); 123 124 HandlerThread handlerThread = new HandlerThread(TAG); 125 handlerThread.start(); 126 mHandler = new Handler(handlerThread.getLooper()); 127 mMyUid = Process.myUid(); 128 129 initializeStaticMocks(); 130 131 setupAppOpsService(); 132 133 mTestStartMillis = System.currentTimeMillis(); 134 } 135 136 @After tearDown()137 public void tearDown() { 138 mAppOpsService.shutdown(); 139 140 mMockingSession.finishMocking(); 141 } 142 initializeStaticMocks()143 private void initializeStaticMocks() { 144 mMockingSession = mockitoSession() 145 .strictness(Strictness.LENIENT) 146 .spyStatic(LocalServices.class) 147 .spyStatic(LocalManagerRegistry.class) 148 .spyStatic(Settings.Global.class) 149 .startMocking(); 150 151 // Mock LocalServices.getService(PackageManagerInternal.class).getPackageStateInternal 152 // and getPackage dependency needed by AppOpsService 153 PackageManagerInternal mockPackageManagerInternal = mock(PackageManagerInternal.class); 154 AndroidPackage mockMyPkg = mock(AndroidPackage.class); 155 when(mockMyPkg.getAttributions()).thenReturn(Collections.emptyList()); 156 PackageStateInternal mockMyPSInternal = mock(PackageStateInternal.class); 157 when(mockMyPSInternal.isPrivileged()).thenReturn(false); 158 when(mockMyPSInternal.getAppId()).thenReturn(mMyUid); 159 when(mockMyPSInternal.getAndroidPackage()).thenReturn(mockMyPkg); 160 161 when(mockPackageManagerInternal.getPackageStateInternal(sMyPackageName)) 162 .thenReturn(mockMyPSInternal); 163 when(mockPackageManagerInternal.getPackage(sMyPackageName)).thenReturn(mockMyPkg); 164 doReturn(mockPackageManagerInternal).when( 165 () -> LocalServices.getService(PackageManagerInternal.class)); 166 167 PackageManagerLocal mockPackageManagerLocal = mock(PackageManagerLocal.class); 168 PackageManagerLocal.UnfilteredSnapshot mockUnfilteredSnapshot = 169 mock(PackageManagerLocal.UnfilteredSnapshot.class); 170 PackageState mockMyPS = mock(PackageState.class); 171 ArrayMap<String, PackageState> packageStates = new ArrayMap<>(); 172 packageStates.put(sMyPackageName, mockMyPS); 173 when(mockMyPS.getAppId()).thenReturn(mMyUid); 174 when(mockUnfilteredSnapshot.getPackageStates()).thenReturn(packageStates); 175 when(mockPackageManagerLocal.withUnfilteredSnapshot()).thenReturn(mockUnfilteredSnapshot); 176 doReturn(mockPackageManagerLocal).when( 177 () -> LocalManagerRegistry.getManager(PackageManagerLocal.class)); 178 179 UserManagerInternal mockUserManagerInternal = mock(UserManagerInternal.class); 180 when(mockUserManagerInternal.getUserIds()).thenReturn(new int[] {getUserId(mMyUid)}); 181 doReturn(mockUserManagerInternal).when( 182 () -> LocalServices.getService(UserManagerInternal.class)); 183 184 // Mock behavior to use specific Settings.Global.APPOP_HISTORY_PARAMETERS 185 doReturn(null).when(() -> Settings.Global.getString(any(ContentResolver.class), 186 eq(Settings.Global.APPOP_HISTORY_PARAMETERS))); 187 } 188 189 @Test testGetOpsForPackage_noOpsLogged()190 public void testGetOpsForPackage_noOpsLogged() { 191 assertThat(getLoggedOps()).isNull(); 192 } 193 194 @Test testNoteOperationAndGetOpsForPackage()195 public void testNoteOperationAndGetOpsForPackage() { 196 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 197 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED); 198 199 // Note an op that's allowed. 200 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 201 List<PackageOps> loggedOps = getLoggedOps(); 202 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 203 204 // Note another op that's not allowed. 205 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null, 206 false); 207 loggedOps = getLoggedOps(); 208 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 209 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED); 210 } 211 212 /** 213 * Tests the scenario where an operation's permission is controlled by another operation. 214 * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the 215 * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed. 216 */ 217 @Test testNoteOperationAndGetOpsForPackage_controlledByDifferentOp()218 public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() { 219 // This op controls WIFI_SCAN 220 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED); 221 222 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false, 223 null, false).getOpMode()).isEqualTo(MODE_ALLOWED); 224 225 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1, 226 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */); 227 228 // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well. 229 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED); 230 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false, 231 null, false).getOpMode()).isEqualTo(MODE_ERRORED); 232 233 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis, 234 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */); 235 } 236 237 // Tests the dumping and restoring of the in-memory state to/from XML. 238 @Test testStatePersistence()239 public void testStatePersistence() { 240 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 241 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED); 242 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 243 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null, 244 false); 245 246 mAppOpsService.shutdown(); 247 248 // Create a new app ops service which will initialize its state from XML. 249 setupAppOpsService(); 250 mAppOpsService.readState(); 251 252 // Query the state of the 2nd service. 253 List<PackageOps> loggedOps = getLoggedOps(); 254 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 255 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED); 256 } 257 258 // Tests that ops are persisted during shutdown. 259 @Test testShutdown()260 public void testShutdown() { 261 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 262 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 263 mAppOpsService.shutdown(); 264 265 // Create a new app ops service which will initialize its state from XML. 266 setupAppOpsService(); 267 268 // Query the state of the 2nd service. 269 List<PackageOps> loggedOps = getLoggedOps(); 270 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 271 } 272 273 @Test testGetOpsForPackage()274 public void testGetOpsForPackage() { 275 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 276 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 277 278 // Query all ops 279 List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage( 280 mMyUid, sMyPackageName, null /* all ops */); 281 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 282 283 // Query specific ops 284 loggedOps = mAppOpsService.getOpsForPackage( 285 mMyUid, sMyPackageName, new int[]{OP_READ_SMS, OP_WRITE_SMS}); 286 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 287 288 // Query unknown UID 289 loggedOps = mAppOpsService.getOpsForPackage(mMyUid + 1, sMyPackageName, null /* all ops */); 290 assertThat(loggedOps).isNull(); 291 292 // Query unknown package name 293 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, "fake.package", null /* all ops */); 294 assertThat(loggedOps).isNull(); 295 296 // Query op code that's not been logged 297 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName, 298 new int[]{OP_WRITE_SMS}); 299 assertThat(loggedOps).isNull(); 300 } 301 302 @Test testPackageRemoved()303 public void testPackageRemoved() { 304 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 305 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 306 307 List<PackageOps> loggedOps = getLoggedOps(); 308 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 309 310 mAppOpsService.packageRemoved(mMyUid, sMyPackageName); 311 assertThat(getLoggedOps()).isNull(); 312 } 313 314 315 /* 316 TODO ntmyren: re enable when we have time to rewrite test. 317 @Test 318 public void testPackageRemovedHistoricalOps() throws InterruptedException { 319 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 320 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 321 322 AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000); 323 historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null, 324 AppOpsManager.UID_STATE_PERSISTENT, 0, 1); 325 326 mAppOpsService.addHistoricalOps(historicalOps); 327 328 AtomicReference<AppOpsManager.HistoricalOps> resultOpsRef = new AtomicReference<>(); 329 AtomicReference<CountDownLatch> latchRef = new AtomicReference<>(new CountDownLatch(1)); 330 RemoteCallback callback = new RemoteCallback(result -> { 331 resultOpsRef.set(result.getParcelable(AppOpsManager.KEY_HISTORICAL_OPS)); 332 latchRef.get().countDown(); 333 }); 334 335 // First, do a fetch to ensure it's written 336 mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null, 337 FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback); 338 339 latchRef.get().await(5, TimeUnit.SECONDS); 340 assertThat(latchRef.get().getCount()).isEqualTo(0); 341 assertThat(resultOpsRef.get().isEmpty()).isFalse(); 342 343 // Then, check it's deleted on removal 344 mAppOpsService.packageRemoved(mMyUid, sMyPackageName); 345 346 latchRef.set(new CountDownLatch(1)); 347 348 mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null, 349 FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback); 350 351 latchRef.get().await(5, TimeUnit.SECONDS); 352 assertThat(latchRef.get().getCount()).isEqualTo(0); 353 assertThat(resultOpsRef.get().isEmpty()).isTrue(); 354 } 355 */ 356 357 @Test testUidRemoved()358 public void testUidRemoved() { 359 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 360 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 361 362 List<PackageOps> loggedOps = getLoggedOps(); 363 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); 364 365 mAppOpsService.uidRemoved(mMyUid); 366 assertThat(getLoggedOps()).isNull(); 367 } 368 369 @Test testUidStateInitializationDoesntClearState()370 public void testUidStateInitializationDoesntClearState() throws InterruptedException { 371 mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); 372 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); 373 mAppOpsService.initializeUidStates(); 374 List<PackageOps> ops = mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName, 375 new int[]{OP_READ_SMS}); 376 assertNotNull(ops); 377 for (int i = 0; i < ops.size(); i++) { 378 List<OpEntry> opEntries = ops.get(i).getOps(); 379 for (int j = 0; j < opEntries.size(); j++) { 380 Map<String, AppOpsManager.AttributedOpEntry> attributedOpEntries = opEntries.get( 381 j).getAttributedOpEntries(); 382 assertNotEquals(-1, attributedOpEntries.get(null) 383 .getLastAccessTime(OP_FLAG_SELF)); 384 } 385 } 386 } 387 getLoggedOps()388 private List<PackageOps> getLoggedOps() { 389 return mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName, null /* all ops */); 390 } 391 assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis, long minRejectMillis, int mode)392 private void assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis, 393 long minRejectMillis, int mode) { 394 395 boolean opLogged = false; 396 for (PackageOps pkgOps : loggedOps) { 397 assertWithMessage("Unexpected UID").that(mMyUid).isEqualTo(pkgOps.getUid()); 398 assertWithMessage("Unexpected package name").that(sMyPackageName).isEqualTo( 399 pkgOps.getPackageName()); 400 401 for (OpEntry opEntry : pkgOps.getOps()) { 402 if (opCode != opEntry.getOp()) { 403 continue; 404 } 405 opLogged = true; 406 407 assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode()); 408 if (minMillis > 0) { 409 assertWithMessage("Unexpected timestamp") 410 .that(opEntry.getLastAccessTime(OP_FLAGS_ALL)).isAtLeast(minMillis); 411 } 412 if (minRejectMillis > 0) { 413 assertWithMessage("Unexpected rejection timestamp").that( 414 opEntry.getLastRejectTime(OP_FLAGS_ALL)).isAtLeast(minRejectMillis); 415 } 416 } 417 } 418 assertWithMessage("Op was not logged").that(opLogged).isTrue(); 419 } 420 } 421