Use location mcc to load the resource
Provide a way for OEM to provide their customization when
device doesn't have a sim card inserted.
Bug: 141406258
Test: 1. Build pass
2. atest NetworkStackTests
Change-Id: Iad8340e643580f8efa536884d22f0ea0a97cd9a5
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9628b3f..b1603d8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -30,6 +30,7 @@
permissions added would cause crashes on startup unless they are also added to the
privileged permissions whitelist for that package. -->
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 478ed6b..13ab04e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -43,4 +43,7 @@
<!-- Customized default DNS Servers address. -->
<string-array name="config_default_dns_servers" translatable="false">
</string-array>
+ <!-- Set to true if NetworkMonitor needs to load the resource by neighbor mcc when device
+ doesn't have a SIM card inserted. -->
+ <bool name="config_no_sim_card_uses_neighbor_mcc">false</bool>
</resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b9d5337..4727215 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -21,6 +21,7 @@
<item type="string" name="config_captive_portal_http_url"/>
<item type="string" name="config_captive_portal_https_url"/>
<item type="array" name="config_captive_portal_fallback_urls"/>
+ <item type="bool" name="config_no_sim_card_uses_neighbor_mcc"/>
<!-- Configuration value for DhcpResults -->
<item type="array" name="config_default_dns_servers"/>
</policy>
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 3dfe004..2c9ffd6 100644
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -76,6 +76,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.DnsResolver;
@@ -101,11 +103,19 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
+import android.telephony.CellInfoTdscdma;
+import android.telephony.CellInfoWcdma;
import android.telephony.CellSignalStrength;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
@@ -116,6 +126,7 @@
import android.util.Pair;
import androidx.annotation.ArrayRes;
+import androidx.annotation.BoolRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -141,8 +152,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@@ -1315,8 +1328,78 @@
CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
}
+ @Nullable
+ private String getMccFromCellInfo(final CellInfo cell) {
+ if (cell instanceof CellInfoGsm) {
+ return ((CellInfoGsm) cell).getCellIdentity().getMccString();
+ } else if (cell instanceof CellInfoLte) {
+ return ((CellInfoLte) cell).getCellIdentity().getMccString();
+ } else if (cell instanceof CellInfoWcdma) {
+ return ((CellInfoWcdma) cell).getCellIdentity().getMccString();
+ } else if (cell instanceof CellInfoTdscdma) {
+ return ((CellInfoTdscdma) cell).getCellIdentity().getMccString();
+ } else if (cell instanceof CellInfoNr) {
+ return ((CellIdentityNr) ((CellInfoNr) cell).getCellIdentity()).getMccString();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return location mcc.
+ */
+ @VisibleForTesting
+ @Nullable
+ protected String getLocationMcc() {
+ // Adding this check is because the new permission won't be granted by mainline update,
+ // the new permission only be granted by OTA for current design. Tracking: b/145774617.
+ if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+ Process.myPid(), Process.myUid())
+ == PackageManager.PERMISSION_DENIED) {
+ log("getLocationMcc : NetworkStack does not hold ACCESS_FINE_LOCATION");
+ return null;
+ }
+ try {
+ final List<CellInfo> cells = mTelephonyManager.getAllCellInfo();
+ final Map<String, Integer> countryCodeMap = new HashMap<>();
+ int maxCount = 0;
+ for (final CellInfo cell : cells) {
+ final String mcc = getMccFromCellInfo(cell);
+ if (mcc != null) {
+ final int count = countryCodeMap.getOrDefault(mcc, 0) + 1;
+ countryCodeMap.put(mcc, count);
+ }
+ }
+ // Return the MCC which occurs most.
+ if (countryCodeMap.size() <= 0) return null;
+ return Collections.max(countryCodeMap.entrySet(),
+ (e1, e2) -> e1.getValue().compareTo(e2.getValue())).getKey();
+ } catch (SecurityException e) {
+ log("Permission is not granted:" + e);
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ protected Context getContextByMccIfNoSimCardOrDefault() {
+ final boolean useNeighborResource =
+ getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc);
+ if (!useNeighborResource
+ || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) {
+ return mContext;
+ }
+ final String mcc = getLocationMcc();
+ if (TextUtils.isEmpty(mcc)) {
+ return mContext;
+ }
+ final Configuration config = mContext.getResources().getConfiguration();
+ config.mcc = Integer.parseInt(mcc);
+ return mContext.createConfigurationContext(config);
+ }
+
private String getCaptivePortalServerHttpsUrl() {
- return getSettingFromResource(mContext, R.string.config_captive_portal_https_url,
+ final Context targetContext = getContextByMccIfNoSimCardOrDefault();
+ return getSettingFromResource(targetContext, R.string.config_captive_portal_https_url,
R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL);
}
@@ -1347,6 +1430,17 @@
}
}
+ @VisibleForTesting
+ protected boolean getResBooleanConfig(@NonNull final Context context,
+ @BoolRes int configResource) {
+ final Resources res = context.getResources();
+ try {
+ return res.getBoolean(configResource);
+ } catch (Resources.NotFoundException e) {
+ return false;
+ }
+ }
+
/**
* Get the captive portal server HTTP URL that is configured on the device.
*
@@ -1355,7 +1449,8 @@
* on one URL that can be used, while NetworkMonitor may implement more complex logic.
*/
public String getCaptivePortalServerHttpUrl() {
- return getSettingFromResource(mContext, R.string.config_captive_portal_http_url,
+ final Context targetContext = getContextByMccIfNoSimCardOrDefault();
+ return getSettingFromResource(targetContext, R.string.config_captive_portal_http_url,
R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL);
}
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 75491ec..b72c579 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -64,7 +64,10 @@
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.DnsResolver;
@@ -87,6 +90,11 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Settings;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
import android.telephony.CellSignalStrength;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
@@ -133,6 +141,7 @@
private static final String LOCATION_HEADER = "location";
private @Mock Context mContext;
+ private @Mock Configuration mConfiguration;
private @Mock Resources mResources;
private @Mock IpConnectivityLog mLogger;
private @Mock SharedLog mValidationLogger;
@@ -484,6 +493,10 @@
assertTrue("NetworkMonitor did not quit after " + HANDLER_TIMEOUT_MS + "ms",
mQuitCv.block(HANDLER_TIMEOUT_MS));
}
+
+ protected Context getContext() {
+ return mContext;
+ }
}
private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
@@ -513,6 +526,45 @@
}
@Test
+ public void testGetLocationMcc() throws Exception {
+ final WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
+ doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkPermission(
+ eq(android.Manifest.permission.ACCESS_FINE_LOCATION), anyInt(), anyInt());
+ assertNull(wnm.getLocationMcc());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
+ eq(android.Manifest.permission.ACCESS_FINE_LOCATION), anyInt(), anyInt());
+ doReturn(new ContextWrapper(mContext)).when(mContext).createConfigurationContext(any());
+ // Prepare CellInfo and check if the vote mechanism is working or not.
+ final CellInfoGsm cellInfoGsm1 = new CellInfoGsm();
+ final CellInfoGsm cellInfoGsm2 = new CellInfoGsm();
+ final CellInfoLte cellInfoLte = new CellInfoLte();
+ final CellIdentityGsm cellIdentityGsm =
+ new CellIdentityGsm(0, 0, 0, 0, "460", "01", "", "");
+ final CellIdentityLte cellIdentityLte =
+ new CellIdentityLte(0, 0, 0, 0, 0, "466", "01", "", "");
+ cellInfoGsm1.setCellIdentity(cellIdentityGsm);
+ cellInfoGsm2.setCellIdentity(cellIdentityGsm);
+ cellInfoLte.setCellIdentity(cellIdentityLte);
+ final List<CellInfo> cellList = new ArrayList<CellInfo>();
+ cellList.add(cellInfoGsm1);
+ cellList.add(cellInfoGsm2);
+ cellList.add(cellInfoLte);
+ doReturn(cellList).when(mTelephony).getAllCellInfo();
+ // The count of 460 is 2 and the count of 466 is 1, so the getLocationMcc() should return
+ // 460.
+ assertEquals("460", wnm.getLocationMcc());
+ // getContextByMccIfNoSimCardOrDefault() shouldn't return mContext when using neighbor mcc
+ // is enabled and the sim is not ready.
+ doReturn(true).when(mResources).getBoolean(R.bool.config_no_sim_card_uses_neighbor_mcc);
+ doReturn(TelephonyManager.SIM_STATE_ABSENT).when(mTelephony).getSimState();
+ doReturn(mConfiguration).when(mResources).getConfiguration();
+ assertEquals(460,
+ wnm.getContextByMccIfNoSimCardOrDefault().getResources().getConfiguration().mcc);
+ doReturn(false).when(mResources).getBoolean(R.bool.config_no_sim_card_uses_neighbor_mcc);
+ assertEquals(wnm.getContext(), wnm.getContextByMccIfNoSimCardOrDefault());
+ }
+
+ @Test
public void testGetIntSetting() throws Exception {
WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();