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