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