Add more checks for location access

Add checks as detailed in the frameworks/base change, and modify
testapps to be able to test the changes.

Bug: 116258458
Test: testapps
Change-Id: I7eec05d80b1dd0b458342f1b6a026f4c0a23e773
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index e251516..d21f369 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -35,6 +35,7 @@
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -156,6 +157,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -1841,9 +1843,21 @@
     public Bundle getCellLocation(String callingPackage) {
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(Binder.getCallingUid(), callingPackage);
-        if (!LocationAccessPolicy.canAccessCellLocation(mApp, callingPackage,
-                Binder.getCallingUid(), Binder.getCallingPid(), true)) {
-            return null;
+
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getCellLocation")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access cell location");
+            case DENIED_SOFT:
+                return new Bundle();
         }
 
         WorkSource workSource = getWorkSource(Binder.getCallingUid());
@@ -1993,9 +2007,21 @@
     public List<CellInfo> getAllCellInfo(String callingPackage) {
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(Binder.getCallingUid(), callingPackage);
-        if (!LocationAccessPolicy.canAccessCellLocation(mApp,
-                callingPackage, Binder.getCallingUid(), Binder.getCallingPid(), true)) {
-            return null;
+
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getAllCellInfo")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access cell info");
+            case DENIED_SOFT:
+                return new ArrayList<>();
         }
 
         final int targetSdk = getTargetSdk(callingPackage);
@@ -2036,9 +2062,21 @@
             int subId, ICellInfoCallback cb, String callingPackage, WorkSource workSource) {
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(Binder.getCallingUid(), callingPackage);
-        if (!LocationAccessPolicy.canAccessCellLocation(mApp, callingPackage,
-                Binder.getCallingUid(), Binder.getCallingPid(), true)) {
-            return;
+
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("requestCellInfoUpdate")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access cell info");
+            case DENIED_SOFT:
+                return;
         }
 
         final Phone phone = getPhone(subId);
@@ -4187,9 +4225,24 @@
      * Scans for available networks.
      */
     @Override
-    public CellNetworkScanResult getCellNetworkScanResults(int subId) {
+    public CellNetworkScanResult getCellNetworkScanResults(int subId, String callingPackage) {
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                 mApp, subId, "getCellNetworkScanResults");
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getCellNetworkScanResults")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access scan results -- location");
+            case DENIED_SOFT:
+                return null;
+        }
 
         long identity = Binder.clearCallingIdentity();
         try {
@@ -4212,17 +4265,29 @@
      */
     @Override
     public int requestNetworkScan(int subId, NetworkScanRequest request, Messenger messenger,
-            IBinder binder) {
+            IBinder binder, String callingPackage) {
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                 mApp, subId, "requestNetworkScan");
 
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mNetworkScanRequestTracker.startNetworkScan(
-                    request, messenger, binder, getPhone(subId));
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("requestNetworkScan")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to request network scan -- location");
+            case DENIED_SOFT:
+                return -1;
         }
+
+        return mNetworkScanRequestTracker.startNetworkScan(
+                request, messenger, binder, getPhone(subId),
+                callingPackage);
     }
 
     /**
@@ -5263,6 +5328,31 @@
             return null;
         }
 
+        LocationAccessPolicy.LocationPermissionResult fineLocationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getServiceStateForSubscriber")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+
+        LocationAccessPolicy.LocationPermissionResult coarseLocationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getServiceStateForSubscriber")
+                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+                                .build());
+        // We don't care about hard or soft here -- all we need to know is how much info to scrub.
+        boolean hasFinePermission =
+                fineLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+        boolean hasCoarsePermission =
+                coarseLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final Phone phone = getPhone(subId);
@@ -5270,7 +5360,13 @@
                 return null;
             }
 
-            return phone.getServiceState();
+            ServiceState ss = phone.getServiceState();
+
+            // Scrub out the location info in ServiceState depending on what level of access
+            // the caller has.
+            if (hasFinePermission) return ss;
+            if (hasCoarsePermission) return ss.sanitizeLocationInfo(false);
+            return ss.sanitizeLocationInfo(true);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml b/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
index 5145a63..6dd8bc2 100644
--- a/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
+++ b/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
@@ -73,7 +73,6 @@
         android:layout_height="50dip">
     </Button>
 
-
     <ScrollView
         android:id="@+id/return_value_wrapper"
         android:layout_width="fill_parent"
diff --git a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 1cfd3ba..550c9f0 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -16,6 +16,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.phone.testapps.telephonyregistry">
+    <uses-sdk android:minSdkVersion="25"
+          android:targetSdkVersion="25"/>
+
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
diff --git a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
index 74cafcd..96f8bf7 100644
--- a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
+++ b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
@@ -24,6 +24,7 @@
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.SparseArray;
 import android.widget.Button;
@@ -76,6 +77,11 @@
             notify("onSrvccStateChanged", srvccState);
         }
 
+        @Override
+        public void onServiceStateChanged(ServiceState state) {
+            notify("onServiceStateChanged", state);
+        }
+
         private void notify(String method, Object data) {
             Notification.Builder builder = new Notification.Builder(TelephonyRegistryTestApp.this,
                     NOTIFICATION_CHANNEL);
diff --git a/tests/src/com/android/phone/LocationAccessPolicyTest.java b/tests/src/com/android/phone/LocationAccessPolicyTest.java
new file mode 100644
index 0000000..9938bf2
--- /dev/null
+++ b/tests/src/com/android/phone/LocationAccessPolicyTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.UserHandle;
+import android.telephony.LocationAccessPolicy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class LocationAccessPolicyTest {
+    private static class Scenario {
+        static class Builder {
+            private int mAppSdkLevel;
+            private boolean mAppHasFineManifest = false;
+            private boolean mAppHasCoarseManifest = false;
+            private int mFineAppOp = AppOpsManager.MODE_IGNORED;
+            private int mCoarseAppOp = AppOpsManager.MODE_IGNORED;
+            private boolean mIsDynamicLocationEnabled;
+            private LocationAccessPolicy.LocationPermissionQuery mQuery;
+            private LocationAccessPolicy.LocationPermissionResult mExpectedResult;
+            private String mName;
+
+            public Builder setAppSdkLevel(int appSdkLevel) {
+                mAppSdkLevel = appSdkLevel;
+                return this;
+            }
+
+            public Builder setAppHasFineManifest(boolean appHasFineManifest) {
+                mAppHasFineManifest = appHasFineManifest;
+                return this;
+            }
+
+            public Builder setAppHasCoarseManifest(
+                    boolean appHasCoarseManifest) {
+                mAppHasCoarseManifest = appHasCoarseManifest;
+                return this;
+            }
+
+            public Builder setFineAppOp(int fineAppOp) {
+                mFineAppOp = fineAppOp;
+                return this;
+            }
+
+            public Builder setCoarseAppOp(int coarseAppOp) {
+                mCoarseAppOp = coarseAppOp;
+                return this;
+            }
+
+            public Builder setIsDynamicLocationEnabled(
+                    boolean isDynamicLocationEnabled) {
+                mIsDynamicLocationEnabled = isDynamicLocationEnabled;
+                return this;
+            }
+
+            public Builder setQuery(
+                    LocationAccessPolicy.LocationPermissionQuery query) {
+                mQuery = query;
+                return this;
+            }
+
+            public Builder setExpectedResult(
+                    LocationAccessPolicy.LocationPermissionResult expectedResult) {
+                mExpectedResult = expectedResult;
+                return this;
+            }
+
+            public Builder setName(String name) {
+                mName = name;
+                return this;
+            }
+
+            public Scenario build() {
+                return new Scenario(mAppSdkLevel, mAppHasFineManifest, mAppHasCoarseManifest,
+                        mFineAppOp, mCoarseAppOp, mIsDynamicLocationEnabled, mQuery,
+                        mExpectedResult, mName);
+            }
+        }
+        int appSdkLevel;
+        boolean appHasFineManifest;
+        boolean appHasCoarseManifest;
+        int fineAppOp;
+        int coarseAppOp;
+        boolean isDynamicLocationEnabled;
+        LocationAccessPolicy.LocationPermissionQuery query;
+        LocationAccessPolicy.LocationPermissionResult expectedResult;
+        String name;
+
+        private Scenario(int appSdkLevel, boolean appHasFineManifest, boolean appHasCoarseManifest,
+                int fineAppOp, int coarseAppOp,
+                boolean isDynamicLocationEnabled,
+                LocationAccessPolicy.LocationPermissionQuery query,
+                LocationAccessPolicy.LocationPermissionResult expectedResult,
+                String name) {
+            this.appSdkLevel = appSdkLevel;
+            this.appHasFineManifest = appHasFineManifest;
+            this.appHasCoarseManifest = appHasFineManifest || appHasCoarseManifest;
+            this.fineAppOp = fineAppOp;
+            this.coarseAppOp = coarseAppOp == AppOpsManager.MODE_ALLOWED ? coarseAppOp : fineAppOp;
+            this.isDynamicLocationEnabled = isDynamicLocationEnabled;
+            this.query = query;
+            this.expectedResult = expectedResult;
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    @Mock Context mContext;
+    @Mock AppOpsManager mAppOpsManager;
+    @Mock LocationManager mLocationManager;
+    @Mock PackageManager mPackageManager;
+    Scenario mScenario;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mockContextSystemService(AppOpsManager.class, mAppOpsManager);
+        mockContextSystemService(LocationManager.class, mLocationManager);
+        mockContextSystemService(PackageManager.class, mPackageManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    private <T> void mockContextSystemService(Class<T> clazz , T obj) {
+        when(mContext.getSystemServiceName(eq(clazz))).thenReturn(clazz.getSimpleName());
+        when(mContext.getSystemService(clazz.getSimpleName())).thenReturn(obj);
+    }
+
+    public LocationAccessPolicyTest(Scenario scenario) {
+        mScenario = scenario;
+    }
+
+
+    @Test
+    public void test() {
+        setupScenario(mScenario);
+        assertEquals(mScenario.expectedResult,
+                LocationAccessPolicy.checkLocationPermission(mContext, mScenario.query));
+    }
+
+    private void setupScenario(Scenario s) {
+        when(mContext.checkPermission(eq(Manifest.permission.ACCESS_FINE_LOCATION),
+                anyInt(), anyInt())).thenReturn(s.appHasFineManifest
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+
+        when(mContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
+                anyInt(), anyInt())).thenReturn(s.appHasCoarseManifest
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+
+        when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OP_FINE_LOCATION),
+                anyInt(), anyString()))
+                .thenReturn(s.fineAppOp);
+        when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OP_COARSE_LOCATION),
+                anyInt(), anyString()))
+                .thenReturn(s.coarseAppOp);
+
+        if (s.isDynamicLocationEnabled) {
+            when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class))).thenReturn(true);
+            when(mContext.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        } else {
+            when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class)))
+                    .thenReturn(false);
+            when(mContext.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        }
+
+        ApplicationInfo fakeAppInfo = new ApplicationInfo();
+        fakeAppInfo.targetSdkVersion = s.appSdkLevel;
+
+        try {
+            when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+                    .thenReturn(fakeAppInfo);
+        } catch (Exception e) {
+            // this is a formality
+        }
+    }
+
+    private static LocationAccessPolicy.LocationPermissionQuery.Builder getDefaultQueryBuilder() {
+        return new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                .setMethod("test")
+                .setCallingPackage("com.android.test")
+                .setCallingPid(10001)
+                .setCallingUid(10001);
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Scenario> getScenarios() {
+        List<Scenario> scenarios = new ArrayList<>();
+        scenarios.add(new Scenario.Builder()
+                .setName("System location is off")
+                .setAppHasFineManifest(true)
+                .setFineAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(false)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_SOFT)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has all proper permissions for fine")
+                .setAppHasFineManifest(true)
+                .setFineAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on older SDK level missing permissions for fine but has coarse")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.JELLY_BEAN)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.M)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.JELLY_BEAN).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level missing fine app ops permission")
+                .setAppHasFineManifest(true)
+                .setFineAppOp(AppOpsManager.MODE_ERRORED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App has coarse permission but fine permission isn't being enforced yet")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(LocationAccessPolicy.MAX_SDK_FOR_ANY_ENFORCEMENT + 1)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(
+                                LocationAccessPolicy.MAX_SDK_FOR_ANY_ENFORCEMENT + 1)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has coarse but missing fine when fine is req.")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.P)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has MODE_IGNORED for app ops on fine")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setFineAppOp(AppOpsManager.MODE_IGNORED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.P)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.O).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App has no permissions but it's sdk level grandfathers it in")
+                .setAppSdkLevel(Build.VERSION_CODES.N)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.O).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has proper permissions for coarse")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.P).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+        return scenarios;
+    }
+}