Merge "ServiceStateProvider:Enforce location permissino check with targetSdkVersion R-"
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
index 08f0907..56786f9 100644
--- a/src/com/android/phone/ServiceStateProvider.java
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -41,6 +41,7 @@
 import android.telephony.LocationAccessPolicy;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -397,6 +398,7 @@
                 return null;
             }
 
+            // TODO(b/182384053): replace targetSdk check with CompatChanges#isChangeEnabled
             final boolean targetingAtLeastS = TelephonyPermissions.getTargetSdk(getContext(),
                     getCallingPackage()) >= Build.VERSION_CODES.S;
             final boolean canReadPrivilegedPhoneState = getContext().checkCallingOrSelfPermission(
@@ -412,14 +414,15 @@
             } else {
                 availableColumns = ALL_COLUMNS;
 
-                final boolean hasLocationPermission =
-                        hasFineLocationPermission() || hasCoarseLocationPermission();
+                final boolean hasLocationPermission = hasLocationPermission();
                 if (hasLocationPermission) {
+                    // No matter the targetSdkVersion, return unredacted ServiceState if caller does
+                    // have location permission.
                     ss = unredactedServiceState;
                 } else {
-                    // The caller has no location permission but explicitly requires for location
-                    // protected columns. Throw SecurityException to fail loudly.
-                    if (projection != null) {
+                    // The caller has targetSdkVersion S+ but no location permission. It explicitly
+                    // requires location protected columns. Throw SecurityException to fail loudly.
+                    if (targetingAtLeastS && projection != null) {
                         for (String requiredColumn : projection) {
                             if (LOCATION_PROTECTED_COLUMNS_SET.contains(requiredColumn)) {
                                 throw new SecurityException("Column " + requiredColumn
@@ -428,15 +431,12 @@
                         }
                     }
 
+                    // In all other cases, return the redacted ServiceState.
                     // The caller has no location permission but only requires columns without
                     // location sensitive info or "all" columns, return result that scrub out all
                     // sensitive info. In later case, we will not know which columns will be fetched
                     // from the returned cursor until the result has been returned.
-                    ss = unredactedServiceState.createLocationInfoSanitizedCopy(
-                            true /*removeCoarseLocation*/);
-                    // TODO(b/188061647): remove the additional redaction once it is fixed in SS
-                    ss.setCdmaSystemAndNetworkId(ServiceState.UNKNOWN_ID,
-                            ServiceState.UNKNOWN_ID);
+                    ss = getLocationRedactedServiceState(unredactedServiceState);
                 }
             }
 
@@ -661,8 +661,12 @@
         return values;
     }
 
-    private boolean hasFineLocationPermission() {
-        LocationAccessPolicy.LocationPermissionResult fineLocationResult =
+    /**
+     * Check location permission with same policy as {@link TelephonyManager#getServiceState()}
+     * which enforces location permission check starting from Q.
+     */
+    private boolean hasLocationPermission() {
+        LocationAccessPolicy.LocationPermissionResult locationPermissionResult =
                 LocationAccessPolicy.checkLocationPermission(getContext(),
                         new LocationAccessPolicy.LocationPermissionQuery.Builder()
                                 .setCallingPackage(getCallingPackage())
@@ -671,27 +675,20 @@
                                 .setCallingUid(Binder.getCallingUid())
                                 .setMethod("ServiceStateProvider#query")
                                 .setLogAsInfo(true)
-                                .setMinSdkVersionForFine(Build.VERSION_CODES.S)
-                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.S)
-                                .setMinSdkVersionForEnforcement(Build.VERSION_CODES.S)
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+                                .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
                                 .build());
-        return fineLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+        return locationPermissionResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
     }
 
-    private boolean hasCoarseLocationPermission() {
-        LocationAccessPolicy.LocationPermissionResult coarseLocationResult =
-                LocationAccessPolicy.checkLocationPermission(getContext(),
-                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
-                                .setCallingPackage(getCallingPackage())
-                                .setCallingFeatureId(getCallingAttributionTag())
-                                .setCallingPid(Binder.getCallingPid())
-                                .setCallingUid(Binder.getCallingUid())
-                                .setMethod("ServiceStateProvider#query")
-                                .setLogAsInfo(true)
-                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.S)
-                                .setMinSdkVersionForFine(Integer.MAX_VALUE)
-                                .setMinSdkVersionForEnforcement(Build.VERSION_CODES.S)
-                                .build());
-        return coarseLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+    // Return a copy of ServiceState with all sensitive info redacted.
+    @VisibleForTesting
+    /* package */ static ServiceState getLocationRedactedServiceState(ServiceState serviceState) {
+        ServiceState ss =
+                serviceState.createLocationInfoSanitizedCopy(true /*removeCoarseLocation*/);
+        // TODO(b/188061647): remove the additional redaction once it is fixed in SS
+        ss.setCdmaSystemAndNetworkId(ServiceState.UNKNOWN_ID, ServiceState.UNKNOWN_ID);
+        return ss;
     }
 }
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
index 76d6ecc..cde584f 100644
--- a/tests/src/com/android/phone/ServiceStateProviderTest.java
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -27,8 +27,6 @@
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
 import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
 
-import static com.android.phone.ServiceStateProvider.DATA_ROAMING_TYPE;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -64,7 +62,9 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -74,15 +74,13 @@
  * Tests for simple queries of ServiceStateProvider.
  *
  * Build, install and run the tests by running the commands below:
- *     runtest --path <dir or file>
- *     runtest --path <dir or file> --test-method <testMethodName>
- *     e.g.)
- *         runtest --path tests/src/com/android/phone/ServiceStateProviderTest.java \
- *                 --test-method testGetServiceState
+ *     atest ServiceStateProviderTest
  */
 @RunWith(AndroidJUnit4.class)
 public class ServiceStateProviderTest {
     private static final String TAG = "ServiceStateProviderTest";
+    private static final int TEST_NETWORK_ID = 123;
+    private static final int TEST_SYSTEM_ID = 123;
 
     private MockContentResolver mContentResolver;
     private ServiceState mTestServiceState;
@@ -117,6 +115,7 @@
 
         mTestServiceState = new ServiceState();
         mTestServiceState.setStateOutOfService();
+        mTestServiceState.setCdmaSystemAndNetworkId(TEST_SYSTEM_ID, TEST_NETWORK_ID);
         mTestServiceStateForSubId1 = new ServiceState();
         mTestServiceStateForSubId1.setStateOff();
 
@@ -153,13 +152,23 @@
         // By default, test with app target R, no READ_PRIVILEGED_PHONE_STATE permission
         setTargetSdkVersion(Build.VERSION_CODES.R);
         setCanReadPrivilegedPhoneState(false);
+
+        // TODO(b/191995565): Turn on all ignored cases once location access is allow to be off
+        // Do not allow phone process to always access location so we can test various scenarios
+        // LocationAccessPolicy.alwaysAllowPrivilegedProcessToAccessLocationForTesting(false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // LocationAccessPolicy.alwaysAllowPrivilegedProcessToAccessLocationForTesting(true);
     }
 
     /**
      * Verify that when calling query with no subId in the uri the default ServiceState is returned.
      * In this case the subId is set to 0 and the expected service state is mTestServiceState.
      */
-    @Test
+    // TODO(b/191995565): Turn this on when location access can be off
+    @Ignore
     @SmallTest
     public void testQueryServiceState_withNoSubId_withoutLocation() {
         setLocationPermissions(false);
@@ -182,7 +191,8 @@
      * returned. In this case the subId is set to 0 and the expected service state is
      * mTestServiceState.
      */
-    @Test
+    // TODO(b/191995565): Turn case on when location access can be off
+    @Ignore
     @SmallTest
     public void testGetServiceState_withDefaultSubId_withoutLocation() {
         setLocationPermissions(false);
@@ -225,11 +235,11 @@
     }
 
     /**
-     * Verify that apps target S+ without READ_PRIVILEGED_PHONE_STATE permission can only access
-     * the public columns of ServiceStateTable.
+     * Verify that apps target S+ without READ_PRIVILEGED_PHONE_STATE permission can access the
+     * public columns of ServiceStateTable.
      */
     @Test
-    public void testQueryAllColumns_targetS_noReadPrivilege() {
+    public void query_publicColumns_targetS_noReadPrivilege_getPublicColumns() {
         setTargetSdkVersion(Build.VERSION_CODES.S);
         setCanReadPrivilegedPhoneState(false);
 
@@ -241,12 +251,12 @@
      * non-public columns should throw IllegalArgumentException.
      */
     @Test
-    public void testQueryNonPublicColumn_targetS_noReadPrivilege() {
+    public void query_hideColumn_targetS_noReadPrivilege_throwIllegalArgumentException() {
         setTargetSdkVersion(Build.VERSION_CODES.S);
         setCanReadPrivilegedPhoneState(false);
 
         // DATA_ROAMING_TYPE is a non-public column
-        String[] projection = new String[]{DATA_ROAMING_TYPE};
+        String[] projection = new String[]{"data_roaming_type"};
 
         assertThrows(IllegalArgumentException.class,
                 () -> verifyServiceStateWithPublicColumns(mTestServiceState, projection));
@@ -257,7 +267,7 @@
      * be able to access all columns.
      */
     @Test
-    public void testQueryAllColumn_targetS_withAllPermission() {
+    public void query_allColumn_targetS_withReadPrivilegedAndLocation_getAllStateUnredacted() {
         setTargetSdkVersion(Build.VERSION_CODES.S);
         setCanReadPrivilegedPhoneState(true);
         setLocationPermissions(true);
@@ -267,6 +277,60 @@
                 mTestServiceState, true /*hasPermission*/);
     }
 
+    /**
+     * Verify that apps target S+ with READ_PRIVILEGED_PHONE_STATE permission but no location
+     * permission, try to access location sensitive columns should throw SecurityException.
+     */
+    // TODO(b/191995565): Turn this on once b/191995565 is integrated
+    @Ignore
+    public void query_locationColumn_targetS_withReadPrivilegeNoLocation_throwSecurityExecption() {
+        setTargetSdkVersion(Build.VERSION_CODES.S);
+        setCanReadPrivilegedPhoneState(true);
+        setLocationPermissions(false);
+
+        // NETWORK_ID is a location-sensitive column
+        String[] projection = new String[]{"network_id"};
+
+        assertThrows(SecurityException.class,
+                () -> verifyServiceStateWithLocationColumns(mTestServiceState, projection));
+    }
+
+    /**
+     * Verify that apps target R- with location permissions should be able to access all columns.
+     */
+    @Test
+    public void query_allColumn_targetR_withLocation_getAllStateUnredacted() {
+        setTargetSdkVersion(Build.VERSION_CODES.R);
+        setLocationPermissions(true);
+
+        verifyServiceStateForSubId(
+                getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                mTestServiceState, true /*hasPermission*/);
+    }
+
+    /**
+     * Verify that apps target R- w/o location permissions should be able to access all columns but
+     * with redacted ServiceState.
+     */
+    // TODO(b/191995565): Turn case on when location access can be off
+    @Ignore
+    public void query_allColumn_targetR_noLocation_getRedacted() {
+        setTargetSdkVersion(Build.VERSION_CODES.R);
+        setLocationPermissions(false);
+
+        verifyServiceStateForSubId(
+                getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                ServiceStateProvider.getLocationRedactedServiceState(mTestServiceState),
+                true /*hasPermission*/);
+    }
+
+    private void verifyServiceStateWithLocationColumns(ServiceState ss, String[] projection) {
+        try (Cursor cursor = mContentResolver.query(ServiceStateTable.CONTENT_URI, projection, null,
+                null)) {
+            assertNotNull(cursor);
+        }
+    }
+
     private void verifyServiceStateWithPublicColumns(ServiceState ss, String[] projection) {
         try (Cursor cursor = mContentResolver.query(ServiceStateTable.CONTENT_URI, projection, null,
                 null)) {