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