• 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.Notification.EXTRA_TITLE;
22 import static android.content.Context.BIND_AUTO_CREATE;
23 import static android.content.Context.BIND_NOT_FOREGROUND;
24 import static android.content.Intent.ACTION_BOOT_COMPLETED;
25 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
26 import static android.location.Criteria.ACCURACY_FINE;
27 import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS;
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 
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertNotEquals;
36 import static org.junit.Assert.assertNotNull;
37 import static org.junit.Assert.assertNull;
38 import static org.junit.Assert.assertTrue;
39 import static org.junit.Assert.fail;
40 import static org.junit.Assume.assumeFalse;
41 import static org.junit.Assume.assumeTrue;
42 
43 import static java.util.concurrent.TimeUnit.MILLISECONDS;
44 
45 import android.app.ActivityManager;
46 import android.app.AppOpsManager;
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.ResolveInfo;
54 import android.location.Criteria;
55 import android.location.Location;
56 import android.location.LocationListener;
57 import android.location.LocationManager;
58 import android.os.Bundle;
59 import android.os.IBinder;
60 import android.os.Looper;
61 import android.os.SystemClock;
62 import android.permission.cts.appthataccesseslocation.IAccessLocationOnCommand;
63 import android.platform.test.annotations.AppModeFull;
64 import android.platform.test.annotations.SecurityTest;
65 import android.provider.DeviceConfig;
66 import android.provider.Settings;
67 import android.service.notification.NotificationListenerService;
68 import android.service.notification.StatusBarNotification;
69 import android.util.Log;
70 
71 import androidx.annotation.NonNull;
72 import androidx.annotation.Nullable;
73 import androidx.test.InstrumentationRegistry;
74 import androidx.test.runner.AndroidJUnit4;
75 
76 import com.android.compatibility.common.util.ProtoUtils;
77 import com.android.compatibility.common.util.mainline.MainlineModule;
78 import com.android.compatibility.common.util.mainline.ModuleDetector;
79 import com.android.server.job.nano.JobSchedulerServiceDumpProto;
80 import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
81 
82 import org.junit.After;
83 import org.junit.AfterClass;
84 import org.junit.Before;
85 import org.junit.BeforeClass;
86 import org.junit.Test;
87 import org.junit.runner.RunWith;
88 
89 import java.util.Arrays;
90 import java.util.concurrent.CountDownLatch;
91 
92 /**
93  * Tests the {@code LocationAccessCheck} in permission controller.
94  */
95 @RunWith(AndroidJUnit4.class)
96 @AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a location "
97         + "access check notification for instant apps.")
98 public class LocationAccessCheckTest {
99     private static final String LOG_TAG = LocationAccessCheckTest.class.getSimpleName();
100 
101     private static final String TEST_APP_PKG = "android.permission.cts.appthataccesseslocation";
102     private static final String TEST_APP_LABEL = "CtsLocationAccess";
103     private static final String TEST_APP_SERVICE = TEST_APP_PKG + ".AccessLocationOnCommand";
104     private static final String TEST_APP_LOCATION_BG_ACCESS_APK =
105             "/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk";
106     private static final String TEST_APP_LOCATION_FG_ACCESS_APK =
107             "/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk";
108 
109     /** Whether to show location access check notifications. */
110     private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
111             "location_access_check_enabled";
112 
113     private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
114     private static final long EXPECTED_TIMEOUT_MILLIS = 1000;
115     private static final long LOCATION_ACCESS_TIMEOUT_MILLIS = 15000;
116     private static final long LOCATION_ACCESS_JOB_WAIT_MILLIS = 250;
117 
118     // Same as in AccessLocationOnCommand
119     private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000;
120 
121     private static final Context sContext = InstrumentationRegistry.getTargetContext();
122     private static final ActivityManager sActivityManager =
123             (ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE);
124     private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
125             .getUiAutomation();
126 
127     private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
128             .getPermissionControllerPackageName();
129 
130     /**
131      * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over
132      * again.
133      */
134     private static Boolean sCanAccessFineLocation = null;
135 
136     private static ServiceConnection sConnection;
137     private static IAccessLocationOnCommand sLocationAccessor;
138 
assumeNotPlayManaged()139     private static void assumeNotPlayManaged() throws Exception {
140         assumeFalse(ModuleDetector.moduleIsPlayManaged(
141                 sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
142     }
143 
144     /**
145      * Connected to {@value #TEST_APP_PKG} and make it access the location in the background
146      */
accessLocation()147     private void accessLocation() throws Throwable {
148         if (sConnection == null || sLocationAccessor == null) {
149             bindService();
150         }
151         eventually(() -> {
152             assertNotNull(sLocationAccessor);
153             sLocationAccessor.accessLocation();
154         }, EXPECTED_TIMEOUT_MILLIS);
155     }
156 
157     /**
158      * A {@link java.util.concurrent.Callable} that can throw a {@link Throwable}
159      */
160     private interface ThrowingCallable<T> {
call()161         T call() throws Throwable;
162     }
163 
164     /**
165      * A {@link Runnable} that can throw a {@link Throwable}
166      */
167     private interface ThrowingRunnable {
run()168         void run() throws Throwable;
169     }
170 
171     /**
172      * Make sure that a {@link ThrowingRunnable} eventually finishes without throwing a {@link
173      * Exception}.
174      *
175      * @param r       The {@link ThrowingRunnable} to run.
176      * @param timeout the maximum time to wait
177      */
eventually(@onNull ThrowingRunnable r, long timeout)178     public static void eventually(@NonNull ThrowingRunnable r, long timeout) throws Throwable {
179         eventually(() -> {
180             r.run();
181             return 0;
182         }, timeout);
183     }
184 
185     /**
186      * Make sure that a {@link ThrowingCallable} eventually finishes without throwing a {@link
187      * Exception}.
188      *
189      * @param r       The {@link ThrowingCallable} to run.
190      * @param timeout the maximum time to wait
191      * @return the return value from the callable
192      * @throws NullPointerException If the return value never becomes non-null
193      */
eventually(@onNull ThrowingCallable<T> r, long timeout)194     public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable {
195         long start = System.currentTimeMillis();
196 
197         while (true) {
198             try {
199                 T res = r.call();
200                 if (res == null) {
201                     throw new NullPointerException("No result");
202                 }
203 
204                 return res;
205             } catch (Throwable e) {
206                 if (System.currentTimeMillis() - start < timeout) {
207                     Log.d(LOG_TAG, "Ignoring exception", e);
208 
209                     Thread.sleep(500);
210                 } else {
211                     throw e;
212                 }
213             }
214         }
215     }
216 
217     /**
218      * Get the state of the job scheduler
219      */
getJobSchedulerDump()220     public static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception {
221         return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class,
222                 ProtoUtils.DUMPSYS_JOB_SCHEDULER);
223     }
224 
225     /**
226      * Clear all data of a package including permissions and files.
227      *
228      * @param pkg The name of the package to be cleared
229      */
clearPackageData(@onNull String pkg)230     private static void clearPackageData(@NonNull String pkg) {
231         unbindService();
232         runShellCommand("pm clear --user -2 " + pkg);
233     }
234 
235     /**
236      * Force a run of the location check.
237      */
runLocationCheck()238     private static void runLocationCheck() {
239         runShellCommand(
240                 "cmd jobscheduler run -u " + android.os.Process.myUserHandle().getIdentifier()
241                         + " -f " + PERMISSION_CONTROLLER_PKG + " 0");
242     }
243 
244     /**
245      * Get a notification thrown by the permission controller that is currently visible.
246      *
247      * @return The notification or {@code null} if there is none
248      */
getPermissionControllerNotification()249     private @Nullable StatusBarNotification getPermissionControllerNotification() throws Exception {
250         NotificationListenerService notificationService = NotificationListener.getInstance();
251 
252         for (StatusBarNotification notification : notificationService.getActiveNotifications()) {
253             if (notification.getPackageName().equals(PERMISSION_CONTROLLER_PKG)) {
254                 return notification;
255             }
256         }
257 
258         return null;
259     }
260 
getNotification(boolean cancelNotification)261     private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
262         return getNotification(cancelNotification, false);
263     }
264 
265     /**
266      * Get a location access notification that is currently visible.
267      *
268      * @param cancelNotification if {@code true} the notification is canceled inside this method
269      * @param returnImmediately if {@code true} this method returns immediately after checking once
270      *                          for the notification
271      * @return The notification or {@code null} if there is none
272      */
getNotification(boolean cancelNotification, boolean returnImmediately)273     private StatusBarNotification getNotification(boolean cancelNotification,
274             boolean returnImmediately) throws Throwable {
275         NotificationListenerService notificationService = NotificationListener.getInstance();
276         long start = SystemClock.elapsedRealtime();
277         long timeout = returnImmediately ? 0 : LOCATION_ACCESS_TIMEOUT_MILLIS
278                 + BACKGROUND_ACCESS_SETTLE_TIME;
279         while (true) {
280             runLocationCheck();
281             Thread.sleep(LOCATION_ACCESS_JOB_WAIT_MILLIS);
282 
283             StatusBarNotification notification = getPermissionControllerNotification();
284             if (notification == null) {
285                 // Sometimes getting a location takes some time, hence not getting a notification
286                 // can be caused by not having gotten a location yet
287                 if (SystemClock.elapsedRealtime() - start < timeout) {
288                     Thread.sleep(LOCATION_ACCESS_JOB_WAIT_MILLIS);
289                     continue;
290                 }
291 
292                 return null;
293             }
294 
295             if (notification.getNotification().extras.getString(EXTRA_TITLE, "")
296                     .contains(TEST_APP_LABEL)) {
297                 if (cancelNotification) {
298                     notificationService.cancelNotification(notification.getKey());
299 
300                     // Wait for notification to get canceled
301                     eventually(() -> assertFalse(
302                             Arrays.asList(notificationService.getActiveNotifications()).contains(
303                                     notification)), UNEXPECTED_TIMEOUT_MILLIS);
304                 }
305 
306                 return notification;
307             } else {
308                 notificationService.cancelNotification(notification.getKey());
309 
310                 // Wait until new notification can be shown
311                 Thread.sleep(200);
312             }
313         }
314     }
315 
316     /**
317      * Grant a permission to the {@value #TEST_APP_PKG}.
318      *
319      * @param permission The permission to grant
320      */
grantPermissionToTestApp(@onNull String permission)321     private void grantPermissionToTestApp(@NonNull String permission) {
322         sUiAutomation.grantRuntimePermission(TEST_APP_PKG, permission);
323     }
324 
325     /**
326      * Register {@link NotificationListener}.
327      */
328     @BeforeClass
allowNotificationAccess()329     public static void allowNotificationAccess() {
330         runShellCommand("cmd notification allow_listener " + (new ComponentName(sContext,
331                 NotificationListener.class).flattenToString()));
332     }
333 
334     /**
335      * Change settings so that permission controller can show location access notifications more
336      * often.
337      */
338     @BeforeClass
reduceDelays()339     public static void reduceDelays() {
340         runWithShellPermissionIdentity(() -> {
341             ContentResolver cr = sContext.getContentResolver();
342 
343             // New settings will be applied in when permission controller is reset
344             Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
345             Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
346         });
347     }
348 
349     @BeforeClass
installBackgroundAccessApp()350     public static void installBackgroundAccessApp() {
351         installBackgroundAccessApp(false);
352     }
353 
installBackgroundAccessApp(boolean isDowngrade)354     private static void installBackgroundAccessApp(boolean isDowngrade) {
355         String command = "pm install -r -g ";
356         if (isDowngrade) {
357             command = command + "-d ";
358         }
359         String output = runShellCommand(command + TEST_APP_LOCATION_BG_ACCESS_APK);
360         assertTrue(output.contains("Success"));
361     }
362 
363     @AfterClass
uninstallBackgroundAccessApp()364     public static void uninstallBackgroundAccessApp() {
365         unbindService();
366         runShellCommand("pm uninstall " + TEST_APP_PKG);
367     }
368 
unbindService()369     private static void unbindService() {
370         if (sConnection != null) {
371             sContext.unbindService(sConnection);
372         }
373         sConnection = null;
374         sLocationAccessor = null;
375     }
376 
377 
installForegroundAccessApp()378     private static void installForegroundAccessApp() {
379         unbindService();
380         runShellCommand("pm install -r -g " + TEST_APP_LOCATION_FG_ACCESS_APK);
381     }
382 
uninstallForegroundAccessApp()383     private static void uninstallForegroundAccessApp() {
384         runShellCommand("pm uninstall " + TEST_APP_LOCATION_FG_ACCESS_APK);
385     }
386 
387     /**
388      * Skip each test for low ram device
389      */
390     @Before
assumeIsNotLowRamDevice()391     public void assumeIsNotLowRamDevice() {
392         assumeFalse(sActivityManager.isLowRamDevice());
393     }
394 
395     @Before
bindService()396     public void bindService() {
397         sConnection = new ServiceConnection() {
398             @Override
399             public void onServiceConnected(ComponentName name, IBinder service) {
400                 sLocationAccessor = IAccessLocationOnCommand.Stub.asInterface(service);
401             }
402 
403             @Override
404             public void onServiceDisconnected(ComponentName name) {
405                 sConnection = null;
406                 sLocationAccessor = null;
407             }
408         };
409 
410         Intent testAppService = new Intent();
411         testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE));
412 
413         sContext.bindService(testAppService, sConnection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
414     }
415 
416     /**
417      * Reset the permission controllers state before each test
418      */
419     @Before
resetPermissionControllerBeforeEachTest()420     public void resetPermissionControllerBeforeEachTest() throws Throwable {
421         resetPermissionController();
422     }
423 
424     /**
425      * Enable location access check
426      */
427     @Before
enableLocationAccessCheck()428     public void enableLocationAccessCheck() {
429         runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
430                 DeviceConfig.NAMESPACE_PRIVACY,
431                 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true", false));
432     }
433 
434     /**
435      * Disable location access check
436      */
disableLocationAccessCheck()437     private void disableLocationAccessCheck() {
438         runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
439                 DeviceConfig.NAMESPACE_PRIVACY,
440                 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "false", false));
441     }
442 
443     /**
444      * Make sure fine location can be accessed at all.
445      */
446     @Before
assumeCanGetFineLocation()447     public void assumeCanGetFineLocation() {
448         if (sCanAccessFineLocation == null) {
449             Criteria crit = new Criteria();
450             crit.setAccuracy(ACCURACY_FINE);
451 
452             CountDownLatch locationCounter = new CountDownLatch(1);
453             sContext.getSystemService(LocationManager.class).requestSingleUpdate(crit,
454                     new LocationListener() {
455                         @Override
456                         public void onLocationChanged(Location location) {
457                             locationCounter.countDown();
458                         }
459 
460                         @Override
461                         public void onStatusChanged(String provider, int status, Bundle extras) {
462                         }
463 
464                         @Override
465                         public void onProviderEnabled(String provider) {
466                         }
467 
468                         @Override
469                         public void onProviderDisabled(String provider) {
470                         }
471                     }, Looper.getMainLooper());
472 
473 
474             try {
475                 sCanAccessFineLocation = locationCounter.await(LOCATION_ACCESS_TIMEOUT_MILLIS,
476                         MILLISECONDS);
477             } catch (InterruptedException ignored) {
478             }
479         }
480 
481         assumeTrue(sCanAccessFineLocation);
482     }
483 
484     /**
485      * Reset the permission controllers state.
486      */
resetPermissionController()487     private static void resetPermissionController() throws Throwable {
488         clearPackageData(PERMISSION_CONTROLLER_PKG);
489         int currentUserId = android.os.Process.myUserHandle().getIdentifier();
490 
491         // Wait until jobs are cleared
492         eventually(() -> {
493             JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
494 
495             for (RegisteredJob job : dump.registeredJobs) {
496                 if (job.dump.sourceUserId == currentUserId) {
497                     assertNotEquals(job.dump.sourcePackageName, PERMISSION_CONTROLLER_PKG);
498                 }
499             }
500         }, UNEXPECTED_TIMEOUT_MILLIS);
501 
502         // Setup up permission controller again (simulate a reboot)
503         Intent permissionControllerSetupIntent = null;
504         for (ResolveInfo ri : sContext.getPackageManager().queryBroadcastReceivers(
505                 new Intent(ACTION_BOOT_COMPLETED), 0)) {
506             String pkg = ri.activityInfo.packageName;
507 
508             if (pkg.equals(PERMISSION_CONTROLLER_PKG)) {
509                 permissionControllerSetupIntent = new Intent()
510                         .setClassName(pkg, ri.activityInfo.name)
511                         .setFlags(FLAG_RECEIVER_FOREGROUND)
512                         .setPackage(PERMISSION_CONTROLLER_PKG);
513 
514                 sContext.sendBroadcast(permissionControllerSetupIntent);
515             }
516         }
517 
518         // Wait until jobs are set up
519         eventually(() -> {
520             JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
521 
522             for (RegisteredJob job : dump.registeredJobs) {
523                 if (job.dump.sourceUserId == currentUserId
524                         && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)
525                         && job.dump.jobInfo.service.className.contains("LocationAccessCheck")) {
526                     return;
527                 }
528             }
529 
530             fail("Permission controller jobs not found");
531         }, UNEXPECTED_TIMEOUT_MILLIS);
532     }
533 
534     /**
535      * Unregister {@link NotificationListener}.
536      */
537     @AfterClass
disallowNotificationAccess()538     public static void disallowNotificationAccess() {
539         runShellCommand("cmd notification disallow_listener " + (new ComponentName(sContext,
540                 NotificationListener.class)).flattenToString());
541     }
542 
543     /**
544      * Reset settings so that permission controller runs normally.
545      */
546     @AfterClass
resetDelays()547     public static void resetDelays() throws Throwable {
548         runWithShellPermissionIdentity(() -> {
549             ContentResolver cr = sContext.getContentResolver();
550 
551             Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
552             Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
553         });
554 
555         resetPermissionController();
556     }
557 
558     /**
559      * Reset location access check
560      */
561     @After
resetPrivacyConfig()562     public void resetPrivacyConfig() {
563         runWithShellPermissionIdentity(
564                 () -> DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS,
565                         DeviceConfig.NAMESPACE_PRIVACY));
566     }
567 
568     @After
locationUnbind()569     public void locationUnbind() throws Throwable {
570         unbindService();
571         getNotification(true, true);
572     }
573 
574     @Test
notificationIsShown()575     public void notificationIsShown() throws Throwable {
576         accessLocation();
577         assertNotNull(getNotification(true));
578     }
579 
580     @Test
581     @SecurityTest(minPatchLevel = "2019-12-01")
notificationIsShownOnlyOnce()582     public void notificationIsShownOnlyOnce() throws Throwable {
583         assumeNotPlayManaged();
584         accessLocation();
585         getNotification(true);
586 
587         assertNull(getNotification(true));
588     }
589 
590     @Test
591     @SecurityTest(minPatchLevel = "2019-12-01")
notificationIsShownAgainAfterClear()592     public void notificationIsShownAgainAfterClear() throws Throwable {
593         assumeNotPlayManaged();
594         accessLocation();
595         getNotification(true);
596 
597         clearPackageData(TEST_APP_PKG);
598 
599         // Wait until package is cleared and permission controller has cleared the state
600         Thread.sleep(10000);
601 
602         // Clearing removed the permissions, hence grant them again
603         grantPermissionToTestApp(ACCESS_FINE_LOCATION);
604         grantPermissionToTestApp(ACCESS_BACKGROUND_LOCATION);
605 
606         accessLocation();
607         assertNotNull(getNotification(true));
608     }
609 
610     @Test
notificationIsShownAgainAfterUninstallAndReinstall()611     public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
612         accessLocation();
613         getNotification(true);
614 
615         uninstallBackgroundAccessApp();
616 
617         // Wait until package permission controller has cleared the state
618         Thread.sleep(2000);
619 
620         installBackgroundAccessApp();
621 
622         eventually(() -> {
623             accessLocation();
624             assertNotNull(getNotification(true));
625         }, UNEXPECTED_TIMEOUT_MILLIS);
626     }
627 
628     @Test
629     @SecurityTest(minPatchLevel = "2019-12-01")
removeNotificationOnUninstall()630     public void removeNotificationOnUninstall() throws Throwable {
631         assumeNotPlayManaged();
632         accessLocation();
633         getNotification(false);
634 
635         uninstallBackgroundAccessApp();
636 
637         try {
638             eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
639         } finally {
640             installBackgroundAccessApp();
641             getNotification(true);
642         }
643     }
644 
645     @Test
notificationIsNotShownAfterAppDoesNotRequestLocationAnymore()646     public void notificationIsNotShownAfterAppDoesNotRequestLocationAnymore() throws Throwable {
647         accessLocation();
648         getNotification(true);
649 
650         // Update to app to a version that does not request permission anymore
651         installForegroundAccessApp();
652 
653         try {
654             resetPermissionController();
655 
656             try {
657                 // We don't expect a notification, but try to trigger one anyway
658                 eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
659             } catch (AssertionError expected) {
660                 return;
661             }
662 
663             fail("Location access notification was shown");
664         } finally {
665             installBackgroundAccessApp(true);
666         }
667     }
668 
669     @Test
670     @SecurityTest(minPatchLevel = "2019-12-01")
noNotificationIfFeatureDisabled()671     public void noNotificationIfFeatureDisabled() throws Throwable {
672         assumeNotPlayManaged();
673         disableLocationAccessCheck();
674         accessLocation();
675         assertNull(getNotification(true));
676     }
677 
678     @Test
679     @SecurityTest(minPatchLevel = "2019-12-01")
notificationOnlyForAccessesSinceFeatureWasEnabled()680     public void notificationOnlyForAccessesSinceFeatureWasEnabled() throws Throwable {
681         assumeNotPlayManaged();
682         // Disable the feature and access location in disabled state
683         getNotification(true, true);
684         disableLocationAccessCheck();
685         accessLocation();
686         assertNull(getNotification(true));
687 
688         // No notification expected for accesses before enabling the feature
689         enableLocationAccessCheck();
690         assertNull(getNotification(true));
691 
692         // Notification expected for access after enabling the feature
693         accessLocation();
694         assertNotNull(getNotification(true));
695     }
696 
697     @Test
698     @SecurityTest(minPatchLevel = "2019-12-01")
noNotificationIfBlamerNotSystemOrLocationProvider()699     public void noNotificationIfBlamerNotSystemOrLocationProvider() throws Throwable {
700         assumeNotPlayManaged();
701         getNotification(true);
702         // Blame the app for access from an untrusted for notification purposes package.
703         runWithShellPermissionIdentity(() -> {
704             AppOpsManager appOpsManager = sContext.getSystemService(AppOpsManager.class);
705             appOpsManager.noteProxyOpNoThrow(AppOpsManager.OPSTR_FINE_LOCATION, TEST_APP_PKG,
706                     sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0));
707         });
708         assertNull(getNotification(true));
709     }
710 
711     @Test
712     @SecurityTest(minPatchLevel = "2019-12-01")
testOpeningLocationSettingsDoesNotTriggerAccess()713     public void testOpeningLocationSettingsDoesNotTriggerAccess() throws Throwable {
714         assumeNotPlayManaged();
715         Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
716         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
717         sContext.startActivity(intent);
718         assertNull(getNotification(true));
719     }
720 }
721