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 17 package android.permission.cts; 18 19 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; 20 import static android.Manifest.permission.ACCESS_FINE_LOCATION; 21 import static android.app.Notification.EXTRA_TITLE; 22 import static android.content.Context.BIND_AUTO_CREATE; 23 import static android.content.Context.BIND_NOT_FOREGROUND; 24 import static android.content.Intent.ACTION_BOOT_COMPLETED; 25 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; 26 import static android.location.Criteria.ACCURACY_FINE; 27 import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS; 28 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS; 29 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS; 30 31 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 32 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 33 34 import static org.junit.Assert.assertFalse; 35 import static org.junit.Assert.assertNotEquals; 36 import static org.junit.Assert.assertNotNull; 37 import static org.junit.Assert.assertNull; 38 import static org.junit.Assert.assertTrue; 39 import static org.junit.Assert.fail; 40 import static org.junit.Assume.assumeFalse; 41 import static org.junit.Assume.assumeTrue; 42 43 import static java.util.concurrent.TimeUnit.MILLISECONDS; 44 45 import android.app.ActivityManager; 46 import android.app.AppOpsManager; 47 import android.app.UiAutomation; 48 import android.content.ComponentName; 49 import android.content.ContentResolver; 50 import android.content.Context; 51 import android.content.Intent; 52 import android.content.ServiceConnection; 53 import android.content.pm.ResolveInfo; 54 import android.location.Criteria; 55 import android.location.Location; 56 import android.location.LocationListener; 57 import android.location.LocationManager; 58 import android.os.Bundle; 59 import android.os.IBinder; 60 import android.os.Looper; 61 import android.os.SystemClock; 62 import android.permission.cts.appthataccesseslocation.IAccessLocationOnCommand; 63 import android.platform.test.annotations.AppModeFull; 64 import android.platform.test.annotations.SecurityTest; 65 import android.provider.DeviceConfig; 66 import android.provider.Settings; 67 import android.service.notification.NotificationListenerService; 68 import android.service.notification.StatusBarNotification; 69 import android.util.Log; 70 71 import androidx.annotation.NonNull; 72 import androidx.annotation.Nullable; 73 import androidx.test.InstrumentationRegistry; 74 import androidx.test.runner.AndroidJUnit4; 75 76 import com.android.compatibility.common.util.ProtoUtils; 77 import com.android.compatibility.common.util.mainline.MainlineModule; 78 import com.android.compatibility.common.util.mainline.ModuleDetector; 79 import com.android.server.job.nano.JobSchedulerServiceDumpProto; 80 import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob; 81 82 import org.junit.After; 83 import org.junit.AfterClass; 84 import org.junit.Before; 85 import org.junit.BeforeClass; 86 import org.junit.Test; 87 import org.junit.runner.RunWith; 88 89 import java.util.Arrays; 90 import java.util.concurrent.CountDownLatch; 91 92 /** 93 * Tests the {@code LocationAccessCheck} in permission controller. 94 */ 95 @RunWith(AndroidJUnit4.class) 96 @AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a location " 97 + "access check notification for instant apps.") 98 public class LocationAccessCheckTest { 99 private static final String LOG_TAG = LocationAccessCheckTest.class.getSimpleName(); 100 101 private static final String TEST_APP_PKG = "android.permission.cts.appthataccesseslocation"; 102 private static final String TEST_APP_LABEL = "CtsLocationAccess"; 103 private static final String TEST_APP_SERVICE = TEST_APP_PKG + ".AccessLocationOnCommand"; 104 private static final String TEST_APP_LOCATION_BG_ACCESS_APK = 105 "/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk"; 106 private static final String TEST_APP_LOCATION_FG_ACCESS_APK = 107 "/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk"; 108 109 /** Whether to show location access check notifications. */ 110 private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = 111 "location_access_check_enabled"; 112 113 private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000; 114 private static final long EXPECTED_TIMEOUT_MILLIS = 1000; 115 private static final long LOCATION_ACCESS_TIMEOUT_MILLIS = 15000; 116 private static final long LOCATION_ACCESS_JOB_WAIT_MILLIS = 250; 117 118 // Same as in AccessLocationOnCommand 119 private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000; 120 121 private static final Context sContext = InstrumentationRegistry.getTargetContext(); 122 private static final ActivityManager sActivityManager = 123 (ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE); 124 private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation() 125 .getUiAutomation(); 126 127 private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager() 128 .getPermissionControllerPackageName(); 129 130 /** 131 * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over 132 * again. 133 */ 134 private static Boolean sCanAccessFineLocation = null; 135 136 private static ServiceConnection sConnection; 137 private static IAccessLocationOnCommand sLocationAccessor; 138 assumeNotPlayManaged()139 private static void assumeNotPlayManaged() throws Exception { 140 assumeFalse(ModuleDetector.moduleIsPlayManaged( 141 sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER)); 142 } 143 144 /** 145 * Connected to {@value #TEST_APP_PKG} and make it access the location in the background 146 */ accessLocation()147 private void accessLocation() throws Throwable { 148 if (sConnection == null || sLocationAccessor == null) { 149 bindService(); 150 } 151 eventually(() -> { 152 assertNotNull(sLocationAccessor); 153 sLocationAccessor.accessLocation(); 154 }, EXPECTED_TIMEOUT_MILLIS); 155 } 156 157 /** 158 * A {@link java.util.concurrent.Callable} that can throw a {@link Throwable} 159 */ 160 private interface ThrowingCallable<T> { call()161 T call() throws Throwable; 162 } 163 164 /** 165 * A {@link Runnable} that can throw a {@link Throwable} 166 */ 167 private interface ThrowingRunnable { run()168 void run() throws Throwable; 169 } 170 171 /** 172 * Make sure that a {@link ThrowingRunnable} eventually finishes without throwing a {@link 173 * Exception}. 174 * 175 * @param r The {@link ThrowingRunnable} to run. 176 * @param timeout the maximum time to wait 177 */ eventually(@onNull ThrowingRunnable r, long timeout)178 public static void eventually(@NonNull ThrowingRunnable r, long timeout) throws Throwable { 179 eventually(() -> { 180 r.run(); 181 return 0; 182 }, timeout); 183 } 184 185 /** 186 * Make sure that a {@link ThrowingCallable} eventually finishes without throwing a {@link 187 * Exception}. 188 * 189 * @param r The {@link ThrowingCallable} to run. 190 * @param timeout the maximum time to wait 191 * @return the return value from the callable 192 * @throws NullPointerException If the return value never becomes non-null 193 */ eventually(@onNull ThrowingCallable<T> r, long timeout)194 public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable { 195 long start = System.currentTimeMillis(); 196 197 while (true) { 198 try { 199 T res = r.call(); 200 if (res == null) { 201 throw new NullPointerException("No result"); 202 } 203 204 return res; 205 } catch (Throwable e) { 206 if (System.currentTimeMillis() - start < timeout) { 207 Log.d(LOG_TAG, "Ignoring exception", e); 208 209 Thread.sleep(500); 210 } else { 211 throw e; 212 } 213 } 214 } 215 } 216 217 /** 218 * Get the state of the job scheduler 219 */ getJobSchedulerDump()220 public static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception { 221 return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class, 222 ProtoUtils.DUMPSYS_JOB_SCHEDULER); 223 } 224 225 /** 226 * Clear all data of a package including permissions and files. 227 * 228 * @param pkg The name of the package to be cleared 229 */ clearPackageData(@onNull String pkg)230 private static void clearPackageData(@NonNull String pkg) { 231 unbindService(); 232 runShellCommand("pm clear --user -2 " + pkg); 233 } 234 235 /** 236 * Force a run of the location check. 237 */ runLocationCheck()238 private static void runLocationCheck() { 239 runShellCommand( 240 "cmd jobscheduler run -u " + android.os.Process.myUserHandle().getIdentifier() 241 + " -f " + PERMISSION_CONTROLLER_PKG + " 0"); 242 } 243 244 /** 245 * Get a notification thrown by the permission controller that is currently visible. 246 * 247 * @return The notification or {@code null} if there is none 248 */ getPermissionControllerNotification()249 private @Nullable StatusBarNotification getPermissionControllerNotification() throws Exception { 250 NotificationListenerService notificationService = NotificationListener.getInstance(); 251 252 for (StatusBarNotification notification : notificationService.getActiveNotifications()) { 253 if (notification.getPackageName().equals(PERMISSION_CONTROLLER_PKG)) { 254 return notification; 255 } 256 } 257 258 return null; 259 } 260 getNotification(boolean cancelNotification)261 private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable { 262 return getNotification(cancelNotification, false); 263 } 264 265 /** 266 * Get a location access notification that is currently visible. 267 * 268 * @param cancelNotification if {@code true} the notification is canceled inside this method 269 * @param returnImmediately if {@code true} this method returns immediately after checking once 270 * for the notification 271 * @return The notification or {@code null} if there is none 272 */ getNotification(boolean cancelNotification, boolean returnImmediately)273 private StatusBarNotification getNotification(boolean cancelNotification, 274 boolean returnImmediately) throws Throwable { 275 NotificationListenerService notificationService = NotificationListener.getInstance(); 276 long start = SystemClock.elapsedRealtime(); 277 long timeout = returnImmediately ? 0 : LOCATION_ACCESS_TIMEOUT_MILLIS 278 + BACKGROUND_ACCESS_SETTLE_TIME; 279 while (true) { 280 runLocationCheck(); 281 Thread.sleep(LOCATION_ACCESS_JOB_WAIT_MILLIS); 282 283 StatusBarNotification notification = getPermissionControllerNotification(); 284 if (notification == null) { 285 // Sometimes getting a location takes some time, hence not getting a notification 286 // can be caused by not having gotten a location yet 287 if (SystemClock.elapsedRealtime() - start < timeout) { 288 Thread.sleep(LOCATION_ACCESS_JOB_WAIT_MILLIS); 289 continue; 290 } 291 292 return null; 293 } 294 295 if (notification.getNotification().extras.getString(EXTRA_TITLE, "") 296 .contains(TEST_APP_LABEL)) { 297 if (cancelNotification) { 298 notificationService.cancelNotification(notification.getKey()); 299 300 // Wait for notification to get canceled 301 eventually(() -> assertFalse( 302 Arrays.asList(notificationService.getActiveNotifications()).contains( 303 notification)), UNEXPECTED_TIMEOUT_MILLIS); 304 } 305 306 return notification; 307 } else { 308 notificationService.cancelNotification(notification.getKey()); 309 310 // Wait until new notification can be shown 311 Thread.sleep(200); 312 } 313 } 314 } 315 316 /** 317 * Grant a permission to the {@value #TEST_APP_PKG}. 318 * 319 * @param permission The permission to grant 320 */ grantPermissionToTestApp(@onNull String permission)321 private void grantPermissionToTestApp(@NonNull String permission) { 322 sUiAutomation.grantRuntimePermission(TEST_APP_PKG, permission); 323 } 324 325 /** 326 * Register {@link NotificationListener}. 327 */ 328 @BeforeClass allowNotificationAccess()329 public static void allowNotificationAccess() { 330 runShellCommand("cmd notification allow_listener " + (new ComponentName(sContext, 331 NotificationListener.class).flattenToString())); 332 } 333 334 /** 335 * Change settings so that permission controller can show location access notifications more 336 * often. 337 */ 338 @BeforeClass reduceDelays()339 public static void reduceDelays() { 340 runWithShellPermissionIdentity(() -> { 341 ContentResolver cr = sContext.getContentResolver(); 342 343 // New settings will be applied in when permission controller is reset 344 Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100); 345 Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50); 346 }); 347 } 348 349 @BeforeClass installBackgroundAccessApp()350 public static void installBackgroundAccessApp() { 351 installBackgroundAccessApp(false); 352 } 353 installBackgroundAccessApp(boolean isDowngrade)354 private static void installBackgroundAccessApp(boolean isDowngrade) { 355 String command = "pm install -r -g "; 356 if (isDowngrade) { 357 command = command + "-d "; 358 } 359 String output = runShellCommand(command + TEST_APP_LOCATION_BG_ACCESS_APK); 360 assertTrue(output.contains("Success")); 361 } 362 363 @AfterClass uninstallBackgroundAccessApp()364 public static void uninstallBackgroundAccessApp() { 365 unbindService(); 366 runShellCommand("pm uninstall " + TEST_APP_PKG); 367 } 368 unbindService()369 private static void unbindService() { 370 if (sConnection != null) { 371 sContext.unbindService(sConnection); 372 } 373 sConnection = null; 374 sLocationAccessor = null; 375 } 376 377 installForegroundAccessApp()378 private static void installForegroundAccessApp() { 379 unbindService(); 380 runShellCommand("pm install -r -g " + TEST_APP_LOCATION_FG_ACCESS_APK); 381 } 382 uninstallForegroundAccessApp()383 private static void uninstallForegroundAccessApp() { 384 runShellCommand("pm uninstall " + TEST_APP_LOCATION_FG_ACCESS_APK); 385 } 386 387 /** 388 * Skip each test for low ram device 389 */ 390 @Before assumeIsNotLowRamDevice()391 public void assumeIsNotLowRamDevice() { 392 assumeFalse(sActivityManager.isLowRamDevice()); 393 } 394 395 @Before bindService()396 public void bindService() { 397 sConnection = new ServiceConnection() { 398 @Override 399 public void onServiceConnected(ComponentName name, IBinder service) { 400 sLocationAccessor = IAccessLocationOnCommand.Stub.asInterface(service); 401 } 402 403 @Override 404 public void onServiceDisconnected(ComponentName name) { 405 sConnection = null; 406 sLocationAccessor = null; 407 } 408 }; 409 410 Intent testAppService = new Intent(); 411 testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE)); 412 413 sContext.bindService(testAppService, sConnection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND); 414 } 415 416 /** 417 * Reset the permission controllers state before each test 418 */ 419 @Before resetPermissionControllerBeforeEachTest()420 public void resetPermissionControllerBeforeEachTest() throws Throwable { 421 resetPermissionController(); 422 } 423 424 /** 425 * Enable location access check 426 */ 427 @Before enableLocationAccessCheck()428 public void enableLocationAccessCheck() { 429 runWithShellPermissionIdentity(() -> DeviceConfig.setProperty( 430 DeviceConfig.NAMESPACE_PRIVACY, 431 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true", false)); 432 } 433 434 /** 435 * Disable location access check 436 */ disableLocationAccessCheck()437 private void disableLocationAccessCheck() { 438 runWithShellPermissionIdentity(() -> DeviceConfig.setProperty( 439 DeviceConfig.NAMESPACE_PRIVACY, 440 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "false", false)); 441 } 442 443 /** 444 * Make sure fine location can be accessed at all. 445 */ 446 @Before assumeCanGetFineLocation()447 public void assumeCanGetFineLocation() { 448 if (sCanAccessFineLocation == null) { 449 Criteria crit = new Criteria(); 450 crit.setAccuracy(ACCURACY_FINE); 451 452 CountDownLatch locationCounter = new CountDownLatch(1); 453 sContext.getSystemService(LocationManager.class).requestSingleUpdate(crit, 454 new LocationListener() { 455 @Override 456 public void onLocationChanged(Location location) { 457 locationCounter.countDown(); 458 } 459 460 @Override 461 public void onStatusChanged(String provider, int status, Bundle extras) { 462 } 463 464 @Override 465 public void onProviderEnabled(String provider) { 466 } 467 468 @Override 469 public void onProviderDisabled(String provider) { 470 } 471 }, Looper.getMainLooper()); 472 473 474 try { 475 sCanAccessFineLocation = locationCounter.await(LOCATION_ACCESS_TIMEOUT_MILLIS, 476 MILLISECONDS); 477 } catch (InterruptedException ignored) { 478 } 479 } 480 481 assumeTrue(sCanAccessFineLocation); 482 } 483 484 /** 485 * Reset the permission controllers state. 486 */ resetPermissionController()487 private static void resetPermissionController() throws Throwable { 488 clearPackageData(PERMISSION_CONTROLLER_PKG); 489 int currentUserId = android.os.Process.myUserHandle().getIdentifier(); 490 491 // Wait until jobs are cleared 492 eventually(() -> { 493 JobSchedulerServiceDumpProto dump = getJobSchedulerDump(); 494 495 for (RegisteredJob job : dump.registeredJobs) { 496 if (job.dump.sourceUserId == currentUserId) { 497 assertNotEquals(job.dump.sourcePackageName, PERMISSION_CONTROLLER_PKG); 498 } 499 } 500 }, UNEXPECTED_TIMEOUT_MILLIS); 501 502 // Setup up permission controller again (simulate a reboot) 503 Intent permissionControllerSetupIntent = null; 504 for (ResolveInfo ri : sContext.getPackageManager().queryBroadcastReceivers( 505 new Intent(ACTION_BOOT_COMPLETED), 0)) { 506 String pkg = ri.activityInfo.packageName; 507 508 if (pkg.equals(PERMISSION_CONTROLLER_PKG)) { 509 permissionControllerSetupIntent = new Intent() 510 .setClassName(pkg, ri.activityInfo.name) 511 .setFlags(FLAG_RECEIVER_FOREGROUND) 512 .setPackage(PERMISSION_CONTROLLER_PKG); 513 514 sContext.sendBroadcast(permissionControllerSetupIntent); 515 } 516 } 517 518 // Wait until jobs are set up 519 eventually(() -> { 520 JobSchedulerServiceDumpProto dump = getJobSchedulerDump(); 521 522 for (RegisteredJob job : dump.registeredJobs) { 523 if (job.dump.sourceUserId == currentUserId 524 && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG) 525 && job.dump.jobInfo.service.className.contains("LocationAccessCheck")) { 526 return; 527 } 528 } 529 530 fail("Permission controller jobs not found"); 531 }, UNEXPECTED_TIMEOUT_MILLIS); 532 } 533 534 /** 535 * Unregister {@link NotificationListener}. 536 */ 537 @AfterClass disallowNotificationAccess()538 public static void disallowNotificationAccess() { 539 runShellCommand("cmd notification disallow_listener " + (new ComponentName(sContext, 540 NotificationListener.class)).flattenToString()); 541 } 542 543 /** 544 * Reset settings so that permission controller runs normally. 545 */ 546 @AfterClass resetDelays()547 public static void resetDelays() throws Throwable { 548 runWithShellPermissionIdentity(() -> { 549 ContentResolver cr = sContext.getContentResolver(); 550 551 Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS); 552 Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS); 553 }); 554 555 resetPermissionController(); 556 } 557 558 /** 559 * Reset location access check 560 */ 561 @After resetPrivacyConfig()562 public void resetPrivacyConfig() { 563 runWithShellPermissionIdentity( 564 () -> DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS, 565 DeviceConfig.NAMESPACE_PRIVACY)); 566 } 567 568 @After locationUnbind()569 public void locationUnbind() throws Throwable { 570 unbindService(); 571 getNotification(true, true); 572 } 573 574 @Test notificationIsShown()575 public void notificationIsShown() throws Throwable { 576 accessLocation(); 577 assertNotNull(getNotification(true)); 578 } 579 580 @Test 581 @SecurityTest(minPatchLevel = "2019-12-01") notificationIsShownOnlyOnce()582 public void notificationIsShownOnlyOnce() throws Throwable { 583 assumeNotPlayManaged(); 584 accessLocation(); 585 getNotification(true); 586 587 assertNull(getNotification(true)); 588 } 589 590 @Test 591 @SecurityTest(minPatchLevel = "2019-12-01") notificationIsShownAgainAfterClear()592 public void notificationIsShownAgainAfterClear() throws Throwable { 593 assumeNotPlayManaged(); 594 accessLocation(); 595 getNotification(true); 596 597 clearPackageData(TEST_APP_PKG); 598 599 // Wait until package is cleared and permission controller has cleared the state 600 Thread.sleep(10000); 601 602 // Clearing removed the permissions, hence grant them again 603 grantPermissionToTestApp(ACCESS_FINE_LOCATION); 604 grantPermissionToTestApp(ACCESS_BACKGROUND_LOCATION); 605 606 accessLocation(); 607 assertNotNull(getNotification(true)); 608 } 609 610 @Test notificationIsShownAgainAfterUninstallAndReinstall()611 public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable { 612 accessLocation(); 613 getNotification(true); 614 615 uninstallBackgroundAccessApp(); 616 617 // Wait until package permission controller has cleared the state 618 Thread.sleep(2000); 619 620 installBackgroundAccessApp(); 621 622 eventually(() -> { 623 accessLocation(); 624 assertNotNull(getNotification(true)); 625 }, UNEXPECTED_TIMEOUT_MILLIS); 626 } 627 628 @Test 629 @SecurityTest(minPatchLevel = "2019-12-01") removeNotificationOnUninstall()630 public void removeNotificationOnUninstall() throws Throwable { 631 assumeNotPlayManaged(); 632 accessLocation(); 633 getNotification(false); 634 635 uninstallBackgroundAccessApp(); 636 637 try { 638 eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS); 639 } finally { 640 installBackgroundAccessApp(); 641 getNotification(true); 642 } 643 } 644 645 @Test notificationIsNotShownAfterAppDoesNotRequestLocationAnymore()646 public void notificationIsNotShownAfterAppDoesNotRequestLocationAnymore() throws Throwable { 647 accessLocation(); 648 getNotification(true); 649 650 // Update to app to a version that does not request permission anymore 651 installForegroundAccessApp(); 652 653 try { 654 resetPermissionController(); 655 656 try { 657 // We don't expect a notification, but try to trigger one anyway 658 eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS); 659 } catch (AssertionError expected) { 660 return; 661 } 662 663 fail("Location access notification was shown"); 664 } finally { 665 installBackgroundAccessApp(true); 666 } 667 } 668 669 @Test 670 @SecurityTest(minPatchLevel = "2019-12-01") noNotificationIfFeatureDisabled()671 public void noNotificationIfFeatureDisabled() throws Throwable { 672 assumeNotPlayManaged(); 673 disableLocationAccessCheck(); 674 accessLocation(); 675 assertNull(getNotification(true)); 676 } 677 678 @Test 679 @SecurityTest(minPatchLevel = "2019-12-01") notificationOnlyForAccessesSinceFeatureWasEnabled()680 public void notificationOnlyForAccessesSinceFeatureWasEnabled() throws Throwable { 681 assumeNotPlayManaged(); 682 // Disable the feature and access location in disabled state 683 getNotification(true, true); 684 disableLocationAccessCheck(); 685 accessLocation(); 686 assertNull(getNotification(true)); 687 688 // No notification expected for accesses before enabling the feature 689 enableLocationAccessCheck(); 690 assertNull(getNotification(true)); 691 692 // Notification expected for access after enabling the feature 693 accessLocation(); 694 assertNotNull(getNotification(true)); 695 } 696 697 @Test 698 @SecurityTest(minPatchLevel = "2019-12-01") noNotificationIfBlamerNotSystemOrLocationProvider()699 public void noNotificationIfBlamerNotSystemOrLocationProvider() throws Throwable { 700 assumeNotPlayManaged(); 701 getNotification(true); 702 // Blame the app for access from an untrusted for notification purposes package. 703 runWithShellPermissionIdentity(() -> { 704 AppOpsManager appOpsManager = sContext.getSystemService(AppOpsManager.class); 705 appOpsManager.noteProxyOpNoThrow(AppOpsManager.OPSTR_FINE_LOCATION, TEST_APP_PKG, 706 sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0)); 707 }); 708 assertNull(getNotification(true)); 709 } 710 711 @Test 712 @SecurityTest(minPatchLevel = "2019-12-01") testOpeningLocationSettingsDoesNotTriggerAccess()713 public void testOpeningLocationSettingsDoesNotTriggerAccess() throws Throwable { 714 assumeNotPlayManaged(); 715 Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); 716 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 717 sContext.startActivity(intent); 718 assertNull(getNotification(true)); 719 } 720 } 721