Merge "Fix multisim issue with sim call manager."
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 92af129..6c69a33 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -38,6 +38,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
import android.view.MenuItem;
@@ -214,10 +215,32 @@
}
};
+ private final ProvisioningManager.Callback mProvisioningCallback =
+ new ProvisioningManager.Callback() {
+ @Override
+ public void onProvisioningIntChanged(int item, int value) {
+ if (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
+ || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED
+ || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED) {
+ updateVtWfc();
+ }
+ }
+ };
+
@Override
protected void onPause() {
super.onPause();
listenPhoneState(false);
+
+ // Remove callback for provisioning changes.
+ try {
+ if (mImsMgr != null) {
+ mImsMgr.getConfigInterface().removeConfigCallback(
+ mProvisioningCallback.getBinder());
+ }
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "onPause: Unable to remove callback for provisioning changes");
+ }
}
@Override
@@ -310,7 +333,24 @@
}
}
}
+ updateVtWfc();
+ // Register callback for provisioning changes.
+ try {
+ if (mImsMgr != null) {
+ mImsMgr.getConfigInterface().addConfigCallback(mProvisioningCallback);
+ }
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "onResume: Unable to register callback for provisioning changes.");
+ }
+ }
+
+ private void updateVtWfc() {
+ PreferenceScreen prefSet = getPreferenceScreen();
+ TelephonyManager telephonyManager = getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mPhone.getSubId());
+ PersistableBundle carrierConfig =
+ PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
if (mImsMgr.isVtEnabledByPlatform() && mImsMgr.isVtProvisionedOnDevice()
&& (carrierConfig.getBoolean(
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS)
@@ -320,6 +360,7 @@
? mImsMgr.isVtEnabledByUser() : false;
mEnableVideoCalling.setChecked(currentValue);
mEnableVideoCalling.setOnPreferenceChangeListener(this);
+ prefSet.addPreference(mEnableVideoCalling);
} else {
prefSet.removePreference(mEnableVideoCalling);
}
@@ -335,6 +376,7 @@
mButtonWifiCalling.setTitle(resolutions.get(0).loadLabel(pm));
mButtonWifiCalling.setSummary(null);
mButtonWifiCalling.setIntent(intent);
+ prefSet.addPreference(mButtonWifiCalling);
} else {
prefSet.removePreference(mButtonWifiCalling);
}
@@ -363,6 +405,7 @@
}
}
mButtonWifiCalling.setSummary(resId);
+ prefSet.addPreference(mButtonWifiCalling);
}
try {
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index df0a527..a7da9db 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -57,6 +57,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccManager;
+import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.feature.ImsFeature;
import android.text.TextUtils;
import android.util.Log;
@@ -891,6 +892,18 @@
}
}
+ private final ProvisioningManager.Callback mProvisioningCallback =
+ new ProvisioningManager.Callback() {
+ @Override
+ public void onProvisioningIntChanged(int item, int value) {
+ if (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
+ || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED
+ || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED) {
+ updateBody();
+ }
+ }
+ };
+
@Override
public void onDestroy() {
super.onDestroy();
@@ -940,6 +953,15 @@
context.getContentResolver().registerContentObserver(ENFORCE_MANAGED_URI, false,
mDpcEnforcedContentObserver);
+ // Register callback for provisioning changes.
+ try {
+ if (mImsMgr != null) {
+ mImsMgr.getConfigInterface().addConfigCallback(mProvisioningCallback);
+ }
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "onResume: Unable to register callback for provisioning changes.");
+ }
+
Log.i(LOG_TAG, "onResume:-");
}
@@ -1332,6 +1354,17 @@
final Context context = getActivity();
context.unregisterReceiver(mPhoneChangeReceiver);
context.getContentResolver().unregisterContentObserver(mDpcEnforcedContentObserver);
+
+ // Remove callback for provisioning changes.
+ try {
+ if (mImsMgr != null) {
+ mImsMgr.getConfigInterface().removeConfigCallback(
+ mProvisioningCallback.getBinder());
+ }
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "onPause: Unable to remove callback for provisioning changes");
+ }
+
if (DBG) log("onPause:-");
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index d080b31..13accc9 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;
@@ -157,6 +158,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;
@@ -1843,9 +1845,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());
@@ -1995,9 +2009,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);
@@ -2038,9 +2064,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);
@@ -4189,9 +4227,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 {
@@ -4214,17 +4267,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);
}
/**
@@ -5265,6 +5330,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);
@@ -5272,7 +5362,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);
}
@@ -5965,9 +6061,15 @@
@Override
public List<UiccCardInfo> getUiccCardsInfo(String callingPackage) {
- if (checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
- != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- throw new SecurityException("Caller does not have carrier privileges on any UICC.");
+ try {
+ enforceReadPrivilegedPermission("getUiccCardsInfo");
+ } catch (SecurityException e) {
+ // even without READ_PRIVILEGED_PHONE_STATE, we allow the call to continue if the caller
+ // has carrier privileges on an active UICC
+ if (checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
+ != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ throw new SecurityException("Caller does not have carrier privileges on any UICC");
+ }
}
final long identity = Binder.clearCallingIdentity();
@@ -6542,4 +6644,24 @@
metrics.updateEnabledModemBitmap((1 << TelephonyManager.from(mApp).getPhoneCount()) - 1);
}
+ @Override
+ public int[] getSlotsMapping() {
+ enforceReadPrivilegedPermission("getSlotsMapping");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int phoneCount = TelephonyManager.getDefault().getPhoneCount();
+ // All logical slots should have a mapping to a physical slot.
+ int[] logicalSlotsMapping = new int[phoneCount];
+ UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+ for (int i = 0; i < slotInfos.length; i++) {
+ if (SubscriptionManager.isValidPhoneId(slotInfos[i].getLogicalSlotIdx())) {
+ logicalSlotsMapping[slotInfos[i].getLogicalSlotIdx()] = i;
+ }
+ }
+ return logicalSlotsMapping;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
diff --git a/testapps/ImsTestService/AndroidManifest.xml b/testapps/ImsTestService/AndroidManifest.xml
index 7662a42..eea54b8 100644
--- a/testapps/ImsTestService/AndroidManifest.xml
+++ b/testapps/ImsTestService/AndroidManifest.xml
@@ -19,6 +19,9 @@
coreApp="true"
package="com.android.phone.testapps.imstestapp">
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <!--Beware, declaring the below permission will cause the device to not boot unless you add
+ this app and permission to frameworks/base/data/etc/privapp-permissions-platform.xml-->
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<application
android:label="ImsTestService"
android:directBootAware="true">
diff --git a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java
index e4a4e6f..84ec7b9 100644
--- a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java
+++ b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
+import android.telephony.AccessNetworkConstants;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsMmTelManager;
@@ -150,8 +151,8 @@
private static final Map<Integer, String> REG_TECH_STRING = new ArrayMap<>(2);
static {
REG_TECH_STRING.put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, "NONE");
- REG_TECH_STRING.put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE, "LTE");
- REG_TECH_STRING.put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, "IWLAN");
+ REG_TECH_STRING.put(AccessNetworkConstants.TransportType.WWAN, "WWAN");
+ REG_TECH_STRING.put(AccessNetworkConstants.TransportType.WLAN, "WLAN");
}
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;
+ }
+}