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.AppOpsManager.OPSTR_FINE_LOCATION; 22 import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; 23 import static android.content.Context.BIND_AUTO_CREATE; 24 import static android.content.Context.BIND_NOT_FOREGROUND; 25 import static android.location.Criteria.ACCURACY_FINE; 26 import static android.os.Process.myUserHandle; 27 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS; 28 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS; 29 30 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 31 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 32 import static com.android.compatibility.common.util.SystemUtil.waitForBroadcasts; 33 34 import static org.junit.Assert.assertNotNull; 35 import static org.junit.Assert.assertNull; 36 import static org.junit.Assert.assertTrue; 37 import static org.junit.Assume.assumeFalse; 38 import static org.junit.Assume.assumeTrue; 39 40 import static java.util.concurrent.TimeUnit.MILLISECONDS; 41 42 import android.app.ActivityManager; 43 import android.app.ActivityOptions; 44 import android.app.AppOpsManager; 45 import android.app.PendingIntent; 46 import android.app.UiAutomation; 47 import android.content.ComponentName; 48 import android.content.ContentResolver; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.ServiceConnection; 52 import android.content.pm.PackageManager; 53 import android.location.Criteria; 54 import android.location.Location; 55 import android.location.LocationListener; 56 import android.location.LocationManager; 57 import android.os.Build; 58 import android.os.Bundle; 59 import android.os.IBinder; 60 import android.os.Looper; 61 import android.os.Process; 62 import android.permission.cts.appthataccesseslocation.IAccessLocationOnCommand; 63 import android.platform.test.annotations.AppModeFull; 64 import android.platform.test.annotations.AsbSecurityTest; 65 import android.platform.test.annotations.SystemUserOnly; 66 import android.provider.DeviceConfig; 67 import android.provider.Settings; 68 import android.service.notification.StatusBarNotification; 69 import android.util.Log; 70 71 import androidx.annotation.NonNull; 72 import androidx.test.InstrumentationRegistry; 73 import androidx.test.filters.SdkSuppress; 74 import androidx.test.runner.AndroidJUnit4; 75 76 import com.android.compatibility.common.util.DeviceConfigStateChangerRule; 77 import com.android.compatibility.common.util.mainline.MainlineModule; 78 import com.android.compatibility.common.util.mainline.ModuleDetector; 79 import com.android.modules.utils.build.SdkLevel; 80 81 import org.junit.After; 82 import org.junit.AfterClass; 83 import org.junit.Before; 84 import org.junit.BeforeClass; 85 import org.junit.Rule; 86 import org.junit.Test; 87 import org.junit.runner.RunWith; 88 89 import java.util.List; 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 private static final String ACTION_SET_UP_LOCATION_ACCESS_CHECK = 109 "com.android.permissioncontroller.action.SET_UP_LOCATION_ACCESS_CHECK"; 110 private static final int LOCATION_ACCESS_CHECK_JOB_ID = 0; 111 private static final int LOCATION_ACCESS_CHECK_NOTIFICATION_ID = 0; 112 113 /** 114 * Whether to show location access check notifications. 115 */ 116 private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = 117 "location_access_check_enabled"; 118 private static final String PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS = 119 "location_access_check_delay_millis"; 120 private static final String PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS = 121 "location_access_check_periodic_interval_millis"; 122 private static final String PROPERTY_BG_LOCATION_CHECK_ENABLED = "bg_location_check_is_enabled"; 123 124 private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000; 125 private static final long EXPECTED_TIMEOUT_MILLIS = 15000; 126 private static final long LOCATION_ACCESS_TIMEOUT_MILLIS = 15000; 127 128 private static final Context sContext = InstrumentationRegistry.getTargetContext(); 129 private static final ActivityManager sActivityManager = 130 sContext.getSystemService(ActivityManager.class); 131 private static final PackageManager sPackageManager = sContext.getPackageManager(); 132 private static final AppOpsManager sAppOpsManager = 133 sContext.getSystemService(AppOpsManager.class); 134 private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation() 135 .getUiAutomation(); 136 137 private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager() 138 .getPermissionControllerPackageName(); 139 private static final String LocationAccessCheckOnBootReceiver = 140 "com.android.permissioncontroller.permission.service" 141 + ".LocationAccessCheck$SetupPeriodicBackgroundLocationAccessCheck"; 142 143 144 /** 145 * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over 146 * again. 147 */ 148 private static Boolean sCanAccessFineLocation = null; 149 150 private static ServiceConnection sConnection; 151 private static IAccessLocationOnCommand sLocationAccessor; 152 assumeNotPlayManaged()153 private static void assumeNotPlayManaged() throws Exception { 154 assumeFalse(ModuleDetector.moduleIsPlayManaged( 155 sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER)); 156 } 157 158 // Override location access check flag 159 @Rule 160 public DeviceConfigStateChangerRule mPrivacyDeviceConfig = 161 new DeviceConfigStateChangerRule(sContext, 162 DeviceConfig.NAMESPACE_PRIVACY, 163 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, 164 Boolean.toString(true)); 165 166 // Override SafetyCenter enabled flag 167 @Rule 168 public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled = 169 new DeviceConfigStateChangerRule(sContext, 170 DeviceConfig.NAMESPACE_PRIVACY, 171 SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED, 172 Boolean.toString(true)); 173 174 // Override BG location enabled flag 175 @Rule 176 public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgLocationCheckEnabled = 177 new DeviceConfigStateChangerRule(sContext, 178 DeviceConfig.NAMESPACE_PRIVACY, 179 PROPERTY_BG_LOCATION_CHECK_ENABLED, 180 Boolean.toString(true)); 181 182 // Override general notification interval 183 @Rule 184 public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckIntervalMillis = 185 new DeviceConfigStateChangerRule(sContext, 186 DeviceConfig.NAMESPACE_PRIVACY, 187 PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS, 188 "100"); 189 190 // Override general delay interval 191 @Rule 192 public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckDelayMillis = 193 new DeviceConfigStateChangerRule(sContext, 194 DeviceConfig.NAMESPACE_PRIVACY, 195 PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS, 196 "50"); 197 198 @BeforeClass beforeClassSetup()199 public static void beforeClassSetup() throws Exception { 200 reduceDelays(); 201 allowNotificationAccess(); 202 installBackgroundAccessApp(); 203 } 204 205 /** 206 * Change settings so that permission controller can show location access notifications more 207 * often. 208 */ reduceDelays()209 public static void reduceDelays() { 210 runWithShellPermissionIdentity(() -> { 211 ContentResolver cr = sContext.getContentResolver(); 212 // New settings will be applied in when permission controller is reset 213 Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100); 214 Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50); 215 }); 216 } 217 218 @AfterClass cleanupAfterClass()219 public static void cleanupAfterClass() throws Throwable { 220 resetDelays(); 221 uninstallTestApp(); 222 disallowNotificationAccess(); 223 } 224 225 /** 226 * Reset settings so that permission controller runs normally. 227 */ resetDelays()228 public static void resetDelays() throws Throwable { 229 runWithShellPermissionIdentity(() -> { 230 ContentResolver cr = sContext.getContentResolver(); 231 Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS); 232 Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS); 233 }); 234 } 235 236 /** 237 * Connected to {@value #TEST_APP_PKG} and make it access the location in the background 238 */ accessLocation()239 private void accessLocation() throws Throwable { 240 if (sConnection == null || sLocationAccessor == null) { 241 bindService(); 242 } 243 244 long beforeAccess = System.currentTimeMillis(); 245 // Wait a little to avoid raciness in timing between threads 246 Thread.sleep(1000); 247 248 // Try again until binder call goes though. It might not go through if the sLocationAccessor 249 // is not bound yet 250 eventually(() -> { 251 assertNotNull(sLocationAccessor); 252 sLocationAccessor.accessLocation(); 253 }, EXPECTED_TIMEOUT_MILLIS); 254 255 // Wait until the access is recorded 256 eventually(() -> { 257 List<AppOpsManager.PackageOps> ops = runWithShellPermissionIdentity( 258 () -> sAppOpsManager.getOpsForPackage( 259 sPackageManager.getPackageUid(TEST_APP_PKG, 0), TEST_APP_PKG, 260 OPSTR_FINE_LOCATION)); 261 262 // Background access must have happened after "beforeAccess" 263 assertTrue(ops.get(0).getOps().get(0).getLastAccessBackgroundTime(OP_FLAGS_ALL_TRUSTED) 264 >= beforeAccess); 265 }, EXPECTED_TIMEOUT_MILLIS); 266 } 267 268 /** 269 * A {@link java.util.concurrent.Callable} that can throw a {@link Throwable} 270 */ 271 private interface ThrowingCallable<T> { call()272 T call() throws Throwable; 273 } 274 275 /** 276 * A {@link Runnable} that can throw a {@link Throwable} 277 */ 278 private interface ThrowingRunnable { run()279 void run() throws Throwable; 280 } 281 282 /** 283 * Make sure that a {@link ThrowingRunnable} eventually finishes without throwing a {@link 284 * Exception}. 285 * 286 * @param r The {@link ThrowingRunnable} to run. 287 * @param timeout the maximum time to wait 288 */ eventually(@onNull ThrowingRunnable r, long timeout)289 public static void eventually(@NonNull ThrowingRunnable r, long timeout) throws Throwable { 290 eventually(() -> { 291 r.run(); 292 return 0; 293 }, timeout); 294 } 295 296 /** 297 * Make sure that a {@link ThrowingCallable} eventually finishes without throwing a {@link 298 * Exception}. 299 * 300 * @param r The {@link ThrowingCallable} to run. 301 * @param timeout the maximum time to wait 302 * @return the return value from the callable 303 * @throws NullPointerException If the return value never becomes non-null 304 */ eventually(@onNull ThrowingCallable<T> r, long timeout)305 public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable { 306 long start = System.currentTimeMillis(); 307 308 while (true) { 309 try { 310 T res = r.call(); 311 if (res == null) { 312 throw new NullPointerException("No result"); 313 } 314 315 return res; 316 } catch (Throwable e) { 317 if (System.currentTimeMillis() - start < timeout) { 318 Log.d(LOG_TAG, "Ignoring exception", e); 319 320 Thread.sleep(500); 321 } else { 322 throw e; 323 } 324 } 325 } 326 } 327 328 /** 329 * Clear all data of a package including permissions and files. 330 * 331 * @param pkg The name of the package to be cleared 332 */ clearPackageData(@onNull String pkg)333 private static void clearPackageData(@NonNull String pkg) { 334 unbindService(); 335 runShellCommand("pm clear --user -2 " + pkg); 336 } 337 isJobReady()338 private static boolean isJobReady() { 339 String jobStatus = runShellCommand("cmd jobscheduler get-job-state -u " 340 + Process.myUserHandle().getIdentifier() + " " + PERMISSION_CONTROLLER_PKG 341 + " " + LOCATION_ACCESS_CHECK_JOB_ID); 342 return jobStatus.contains("waiting"); 343 } 344 345 /** 346 * Force a run of the location check. 347 */ runLocationCheck()348 private static void runLocationCheck() throws Throwable { 349 if (!isJobReady()) { 350 PermissionUtils.scheduleJob(sUiAutomation, PERMISSION_CONTROLLER_PKG, 351 LOCATION_ACCESS_CHECK_JOB_ID, EXPECTED_TIMEOUT_MILLIS, 352 ACTION_SET_UP_LOCATION_ACCESS_CHECK, LocationAccessCheckOnBootReceiver); 353 } 354 355 TestUtils.awaitJobUntilRequestedState( 356 PERMISSION_CONTROLLER_PKG, 357 LOCATION_ACCESS_CHECK_JOB_ID, 358 EXPECTED_TIMEOUT_MILLIS, 359 sUiAutomation, 360 "waiting" 361 ); 362 363 TestUtils.runJobAndWaitUntilCompleted( 364 PERMISSION_CONTROLLER_PKG, 365 LOCATION_ACCESS_CHECK_JOB_ID, 366 EXPECTED_TIMEOUT_MILLIS, 367 sUiAutomation 368 ); 369 } 370 371 /** 372 * Get a location access notification that is currently visible. 373 * 374 * @param cancelNotification if {@code true} the notification is canceled inside this method 375 * @return The notification or {@code null} if there is none 376 */ getNotification(boolean cancelNotification)377 private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable { 378 return CtsNotificationListenerServiceUtils.getNotificationForPackageAndId( 379 PERMISSION_CONTROLLER_PKG, LOCATION_ACCESS_CHECK_NOTIFICATION_ID, 380 cancelNotification); 381 } 382 383 /** 384 * Grant a permission to the {@value #TEST_APP_PKG}. 385 * 386 * @param permission The permission to grant 387 */ grantPermissionToTestApp(@onNull String permission)388 private void grantPermissionToTestApp(@NonNull String permission) { 389 sUiAutomation.grantRuntimePermission(TEST_APP_PKG, permission); 390 } 391 392 /** 393 * Register {@link CtsNotificationListenerService}. 394 */ allowNotificationAccess()395 public static void allowNotificationAccess() { 396 runShellCommand("cmd notification allow_listener " + (new ComponentName(sContext, 397 CtsNotificationListenerService.class).flattenToString())); 398 } 399 installBackgroundAccessApp()400 public static void installBackgroundAccessApp() throws Exception { 401 String output = runShellCommand("pm install -r -g " + TEST_APP_LOCATION_BG_ACCESS_APK); 402 assertTrue(output.contains("Success")); 403 // Wait for user sensitive to be updated, which is checked by LocationAccessCheck. 404 Thread.sleep(5000); 405 } 406 uninstallTestApp()407 public static void uninstallTestApp() { 408 unbindService(); 409 runShellCommand("pm uninstall " + TEST_APP_PKG); 410 } 411 unbindService()412 private static void unbindService() { 413 if (sConnection != null) { 414 sContext.unbindService(sConnection); 415 } 416 sConnection = null; 417 sLocationAccessor = null; 418 } 419 setDeviceConfigProperty( @onNull String propertyName, @NonNull String value)420 private void setDeviceConfigProperty( 421 @NonNull String propertyName, 422 @NonNull String value) { 423 runWithShellPermissionIdentity(() -> { 424 boolean valueWasSet = DeviceConfig.setProperty( 425 DeviceConfig.NAMESPACE_PRIVACY, 426 propertyName, 427 value, 428 false); 429 if (!valueWasSet) { 430 throw new IllegalStateException("Could not set " + propertyName + " to " + value); 431 } 432 }); 433 } 434 435 installForegroundAccessApp()436 private static void installForegroundAccessApp() throws Exception { 437 unbindService(); 438 runShellCommand("pm install -r -g " + TEST_APP_LOCATION_FG_ACCESS_APK); 439 // Wait for user sensitive to be updated, which is checked by LocationAccessCheck. 440 Thread.sleep(5000); 441 } 442 443 /** 444 * Skip each test for low ram device 445 */ assumeIsNotLowRamDevice()446 public void assumeIsNotLowRamDevice() { 447 assumeFalse(sActivityManager.isLowRamDevice()); 448 } 449 wakeUpAndDismissKeyguard()450 public void wakeUpAndDismissKeyguard() { 451 runShellCommand("input keyevent KEYCODE_WAKEUP"); 452 runShellCommand("wm dismiss-keyguard"); 453 } 454 bindService()455 public void bindService() { 456 sConnection = new ServiceConnection() { 457 @Override 458 public void onServiceConnected(ComponentName name, IBinder service) { 459 sLocationAccessor = IAccessLocationOnCommand.Stub.asInterface(service); 460 } 461 462 @Override 463 public void onServiceDisconnected(ComponentName name) { 464 sConnection = null; 465 sLocationAccessor = null; 466 } 467 }; 468 469 Intent testAppService = new Intent(); 470 testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE)); 471 472 sContext.bindService(testAppService, sConnection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND); 473 } 474 475 @Before beforeEachTestSetup()476 public void beforeEachTestSetup() throws Throwable { 477 assumeIsNotLowRamDevice(); 478 wakeUpAndDismissKeyguard(); 479 bindService(); 480 resetPermissionControllerBeforeEachTest(); 481 bypassBatterySavingRestrictions(); 482 assumeCanGetFineLocation(); 483 } 484 485 /** 486 * Reset the permission controllers state before each test 487 */ resetPermissionControllerBeforeEachTest()488 public void resetPermissionControllerBeforeEachTest() throws Throwable { 489 // Has to be before resetPermissionController to make sure enablement time is the reset time 490 // of permission controller 491 enableLocationAccessCheck(); 492 493 resetPermissionController(); 494 495 eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS); 496 497 // Reset job scheduler stats (to allow more jobs to be run) 498 runShellCommand( 499 "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " " 500 + PERMISSION_CONTROLLER_PKG); 501 runShellCommand("cmd jobscheduler reset-schedule-quota"); 502 } 503 bypassBatterySavingRestrictions()504 public void bypassBatterySavingRestrictions() { 505 runShellCommand("cmd tare set-vip " + myUserHandle().getIdentifier() 506 + " " + PERMISSION_CONTROLLER_PKG + " true"); 507 } 508 509 /** 510 * Enable location access check 511 */ enableLocationAccessCheck()512 public void enableLocationAccessCheck() throws Throwable { 513 setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, 514 "true"); 515 // Run a location access check to update enabled state inside permission controller 516 runLocationCheck(); 517 } 518 519 /** 520 * Disable location access check 521 */ disableLocationAccessCheck()522 private void disableLocationAccessCheck() throws Throwable { 523 setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, 524 "false"); 525 // Run a location access check to update enabled state inside permission controller 526 runLocationCheck(); 527 } 528 529 /** 530 * Make sure fine location can be accessed at all. 531 */ assumeCanGetFineLocation()532 public void assumeCanGetFineLocation() { 533 if (sCanAccessFineLocation == null) { 534 Criteria crit = new Criteria(); 535 crit.setAccuracy(ACCURACY_FINE); 536 537 CountDownLatch locationCounter = new CountDownLatch(1); 538 sContext.getSystemService(LocationManager.class).requestSingleUpdate(crit, 539 new LocationListener() { 540 @Override 541 public void onLocationChanged(Location location) { 542 locationCounter.countDown(); 543 } 544 545 @Override 546 public void onStatusChanged(String provider, int status, Bundle extras) { 547 } 548 549 @Override 550 public void onProviderEnabled(String provider) { 551 } 552 553 @Override 554 public void onProviderDisabled(String provider) { 555 } 556 }, Looper.getMainLooper()); 557 558 559 try { 560 sCanAccessFineLocation = locationCounter.await(LOCATION_ACCESS_TIMEOUT_MILLIS, 561 MILLISECONDS); 562 } catch (InterruptedException ignored) { 563 } 564 } 565 566 assumeTrue(sCanAccessFineLocation); 567 } 568 569 /** 570 * Reset the permission controllers state. 571 */ resetPermissionController()572 private static void resetPermissionController() throws Throwable { 573 unbindService(); 574 PermissionUtils.resetPermissionControllerJob(sUiAutomation, PERMISSION_CONTROLLER_PKG, 575 LOCATION_ACCESS_CHECK_JOB_ID, 45000, 576 ACTION_SET_UP_LOCATION_ACCESS_CHECK, LocationAccessCheckOnBootReceiver); 577 } 578 579 /** 580 * Unregister {@link CtsNotificationListenerService}. 581 */ disallowNotificationAccess()582 public static void disallowNotificationAccess() { 583 runShellCommand("cmd notification disallow_listener " + (new ComponentName(sContext, 584 CtsNotificationListenerService.class)).flattenToString()); 585 } 586 587 @After cleanupAfterEachTest()588 public void cleanupAfterEachTest() throws Throwable { 589 resetPrivacyConfig(); 590 locationUnbind(); 591 resetBatterySavingRestrictions(); 592 } 593 594 /** 595 * Reset location access check 596 */ resetPrivacyConfig()597 public void resetPrivacyConfig() throws Throwable { 598 // Run a location access check to update enabled state inside permission controller 599 runLocationCheck(); 600 } 601 locationUnbind()602 public void locationUnbind() throws Throwable { 603 unbindService(); 604 } 605 resetBatterySavingRestrictions()606 public void resetBatterySavingRestrictions() { 607 runShellCommand("cmd tare set-vip " + myUserHandle().getIdentifier() 608 + " " + PERMISSION_CONTROLLER_PKG + " default"); 609 } 610 611 @Test notificationIsShown()612 public void notificationIsShown() throws Throwable { 613 accessLocation(); 614 runLocationCheck(); 615 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 616 } 617 618 @Test 619 @AsbSecurityTest(cveBugId = 141028068) notificationIsShownOnlyOnce()620 public void notificationIsShownOnlyOnce() throws Throwable { 621 assumeNotPlayManaged(); 622 623 accessLocation(); 624 runLocationCheck(); 625 626 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 627 628 accessLocation(); 629 runLocationCheck(); 630 631 assertNull(getNotification(true)); 632 } 633 634 @SystemUserOnly(reason = "b/172259935") 635 @Test 636 @AsbSecurityTest(cveBugId = 141028068) notificationIsShownAgainAfterClear()637 public void notificationIsShownAgainAfterClear() throws Throwable { 638 assumeNotPlayManaged(); 639 accessLocation(); 640 runLocationCheck(); 641 642 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 643 644 clearPackageData(TEST_APP_PKG); 645 646 // Wait until package is cleared and permission controller has cleared the state 647 Thread.sleep(10000); 648 waitForBroadcasts(); 649 650 // Clearing removed the permissions, hence grant them again 651 grantPermissionToTestApp(ACCESS_FINE_LOCATION); 652 grantPermissionToTestApp(ACCESS_BACKGROUND_LOCATION); 653 654 accessLocation(); 655 runLocationCheck(); 656 657 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 658 } 659 660 @SystemUserOnly(reason = "b/172259935") 661 @Test notificationIsShownAgainAfterUninstallAndReinstall()662 public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable { 663 accessLocation(); 664 runLocationCheck(); 665 666 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 667 668 uninstallTestApp(); 669 670 // Wait until package permission controller has cleared the state 671 Thread.sleep(2000); 672 673 installBackgroundAccessApp(); 674 waitForBroadcasts(); 675 accessLocation(); 676 runLocationCheck(); 677 678 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 679 } 680 681 @Test 682 @AsbSecurityTest(cveBugId = 141028068) removeNotificationOnUninstall()683 public void removeNotificationOnUninstall() throws Throwable { 684 assumeNotPlayManaged(); 685 686 accessLocation(); 687 runLocationCheck(); 688 689 eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS); 690 691 uninstallTestApp(); 692 // wait for permission controller (broadcast receiver) to clean up things 693 Thread.sleep(5000); 694 waitForBroadcasts(); 695 696 try { 697 eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS); 698 } finally { 699 installBackgroundAccessApp(); 700 } 701 } 702 703 @Test notificationIsNotShownAfterAppDoesNotRequestLocationAnymore()704 public void notificationIsNotShownAfterAppDoesNotRequestLocationAnymore() throws Throwable { 705 accessLocation(); 706 runLocationCheck(); 707 708 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 709 710 // Update to app to a version that does not request permission anymore 711 installForegroundAccessApp(); 712 713 try { 714 resetPermissionController(); 715 716 runLocationCheck(); 717 718 // We don't expect a notification, but try to trigger one anyway 719 assertNull(getNotification(false)); 720 } finally { 721 installBackgroundAccessApp(); 722 } 723 } 724 725 @Test 726 @AsbSecurityTest(cveBugId = 141028068) noNotificationIfFeatureDisabled()727 public void noNotificationIfFeatureDisabled() throws Throwable { 728 assumeNotPlayManaged(); 729 730 disableLocationAccessCheck(); 731 732 accessLocation(); 733 runLocationCheck(); 734 735 assertNull(getNotification(false)); 736 } 737 738 @Test 739 @AsbSecurityTest(cveBugId = 141028068) notificationOnlyForAccessesSinceFeatureWasEnabled()740 public void notificationOnlyForAccessesSinceFeatureWasEnabled() throws Throwable { 741 assumeNotPlayManaged(); 742 743 disableLocationAccessCheck(); 744 745 accessLocation(); 746 runLocationCheck(); 747 748 // No notification expected for accesses before enabling the feature 749 assertNull(getNotification(false)); 750 751 enableLocationAccessCheck(); 752 Thread.sleep(2000); 753 754 // Trigger update of location enable time. In the real world it enabling happens on the 755 // first location check. I.e. accesses before this location check are ignored. 756 runLocationCheck(); 757 758 // No notification expected for accesses before enabling the feature (even after feature is 759 // enabled now) 760 assertNull(getNotification(false)); 761 762 // Notification expected for access after enabling the feature 763 accessLocation(); 764 runLocationCheck(); 765 766 eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS); 767 } 768 769 @Test 770 @AsbSecurityTest(cveBugId = 141028068) noNotificationIfBlamerNotSystemOrLocationProvider()771 public void noNotificationIfBlamerNotSystemOrLocationProvider() throws Throwable { 772 assumeNotPlayManaged(); 773 774 // Blame the app for access from an untrusted for notification purposes package. 775 runWithShellPermissionIdentity(() -> { 776 AppOpsManager appOpsManager = sContext.getSystemService(AppOpsManager.class); 777 appOpsManager.noteProxyOpNoThrow(OPSTR_FINE_LOCATION, TEST_APP_PKG, 778 sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0)); 779 }); 780 runLocationCheck(); 781 782 assertNull(getNotification(false)); 783 } 784 785 @Test 786 @AsbSecurityTest(cveBugId = 141028068) testOpeningLocationSettingsDoesNotTriggerAccess()787 public void testOpeningLocationSettingsDoesNotTriggerAccess() throws Throwable { 788 assumeNotPlayManaged(); 789 790 Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); 791 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 792 sContext.startActivity(intent); 793 794 runLocationCheck(); 795 assertNull(getNotification(false)); 796 } 797 798 @Test 799 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") notificationOnClickOpensSafetyCenter()800 public void notificationOnClickOpensSafetyCenter() throws Throwable { 801 assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext)); 802 accessLocation(); 803 runLocationCheck(); 804 805 StatusBarNotification currentNotification = eventually(() -> { 806 StatusBarNotification notification = getNotification(false); 807 assertNotNull(notification); 808 return notification; 809 }, EXPECTED_TIMEOUT_MILLIS); 810 811 // Verify content intent 812 PendingIntent contentIntent = currentNotification.getNotification().contentIntent; 813 if (SdkLevel.isAtLeastU()) { 814 contentIntent.send(null, 0, null, null, null, null, 815 ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( 816 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle()); 817 } else { 818 contentIntent.send(); 819 } 820 821 SafetyCenterUtils.assertSafetyCenterStarted(); 822 } 823 } 824