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