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