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