1 package com.android.carrierconfig; 2 3 import android.annotation.Nullable; 4 import android.content.Context; 5 import android.os.Build; 6 import android.os.PersistableBundle; 7 import android.service.carrier.CarrierIdentifier; 8 import android.service.carrier.CarrierService; 9 import android.telephony.TelephonyManager; 10 import android.text.TextUtils; 11 import android.util.Log; 12 13 import org.xmlpull.v1.XmlPullParser; 14 import org.xmlpull.v1.XmlPullParserException; 15 import org.xmlpull.v1.XmlPullParserFactory; 16 17 import java.io.IOException; 18 import java.util.regex.Matcher; 19 import java.util.regex.Pattern; 20 21 /** 22 * Provides network overrides for carrier configuration. 23 * 24 * The configuration available through CarrierConfigManager is a combination of default values, 25 * default network overrides, and carrier overrides. The default network overrides are provided by 26 * this service. For a given network, we look for a matching XML file in our assets folder, and 27 * return the PersistableBundle from that file. Assets are preferred over Resources because resource 28 * overlays only support using MCC+MNC and that doesn't work with MVNOs. The only resource file used 29 * is vendor.xml, to provide vendor-specific overrides. 30 */ 31 public class DefaultCarrierConfigService extends CarrierService { 32 33 private static final String SPN_EMPTY_MATCH = "null"; 34 35 private static final String CARRIER_ID_PREFIX = "carrier_config_carrierid_"; 36 37 private static final String MCCMNC_PREFIX = "carrier_config_mccmnc_"; 38 39 private static final String TAG = "DefaultCarrierConfigService"; 40 41 private XmlPullParserFactory mFactory; 42 DefaultCarrierConfigService()43 public DefaultCarrierConfigService() { 44 Log.d(TAG, "Service created"); 45 mFactory = null; 46 } 47 48 /** 49 * Returns per-network overrides for carrier configuration. 50 * 51 * This returns a carrier config bundle appropriate for the given carrier by reading data from 52 * files in our assets folder. Config files in assets folder are carrier-id-indexed 53 * {@link TelephonyManager#getSimCarrierId()}. NOTE: config files named after mccmnc 54 * are for those without a matching carrier id and should be renamed to carrier id once the 55 * missing IDs are added to 56 * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">carrier id list</a> 57 * 58 * First, look for file named after 59 * carrier_config_carrierid_<carrierid>_<carriername>.xml if carrier id is not 60 * {@link TelephonyManager#UNKNOWN_CARRIER_ID}. Note <carriername> is to improve the 61 * readability which should not be used to search asset files. If there is no configuration, 62 * then we look for a file named after the MCC+MNC of {@code id} as a fallback. Last, we read 63 * res/xml/vendor.xml. 64 * 65 * carrierid.xml doesn't support multiple bundles with filters as each carrier including MVNOs 66 * has its own config file named after its carrier id. 67 * Both vendor.xml and MCC+MNC.xml files may contain multiple bundles with filters on them. 68 * All the matching bundles are flattened to return one carrier config bundle. 69 */ 70 @Override onLoadConfig(CarrierIdentifier id)71 public PersistableBundle onLoadConfig(CarrierIdentifier id) { 72 Log.d(TAG, "Config being fetched"); 73 74 if (id == null) { 75 return null; 76 } 77 78 PersistableBundle config = new PersistableBundle(); 79 try { 80 synchronized (this) { 81 if (mFactory == null) { 82 mFactory = XmlPullParserFactory.newInstance(); 83 } 84 } 85 86 XmlPullParser parser = mFactory.newPullParser(); 87 if (id.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) { 88 PersistableBundle configByCarrierId = new PersistableBundle(); 89 PersistableBundle configBySpecificCarrierId = new PersistableBundle(); 90 PersistableBundle configByMccMncFallBackCarrierId = new PersistableBundle(); 91 TelephonyManager telephonyManager = getApplicationContext() 92 .getSystemService(TelephonyManager.class); 93 int mccmncCarrierId = telephonyManager 94 .getCarrierIdFromMccMnc(id.getMcc() + id.getMnc()); 95 for (String file : getApplicationContext().getAssets().list("")) { 96 if (file.startsWith(CARRIER_ID_PREFIX + id.getSpecificCarrierId() + "_")) { 97 parser.setInput(getApplicationContext().getAssets().open(file), "utf-8"); 98 configBySpecificCarrierId = readConfigFromXml(parser, null); 99 break; 100 } else if (file.startsWith(CARRIER_ID_PREFIX + id.getCarrierId() + "_")) { 101 parser.setInput(getApplicationContext().getAssets().open(file), "utf-8"); 102 configByCarrierId = readConfigFromXml(parser, null); 103 } else if (file.startsWith(CARRIER_ID_PREFIX + mccmncCarrierId + "_")) { 104 parser.setInput(getApplicationContext().getAssets().open(file), "utf-8"); 105 configByMccMncFallBackCarrierId = readConfigFromXml(parser, null); 106 } 107 } 108 109 // priority: specific carrier id > carrier id > mccmnc fallback carrier id 110 if (!configBySpecificCarrierId.isEmpty()) { 111 config = configBySpecificCarrierId; 112 } else if (!configByCarrierId.isEmpty()) { 113 config = configByCarrierId; 114 } else if (!configByMccMncFallBackCarrierId.isEmpty()) { 115 config = configByMccMncFallBackCarrierId; 116 } 117 } 118 if (config.isEmpty()) { 119 // fallback to use mccmnc.xml when there is no carrier id named configuration found. 120 parser.setInput(getApplicationContext().getAssets().open( 121 MCCMNC_PREFIX + id.getMcc() + id.getMnc() + ".xml"), "utf-8"); 122 config = readConfigFromXml(parser, id); 123 } 124 125 } 126 catch (IOException | XmlPullParserException e) { 127 Log.d(TAG, e.toString()); 128 // We can return an empty config for unknown networks. 129 config = new PersistableBundle(); 130 } 131 132 // Treat vendor.xml as if it were appended to the carrier config file we read. 133 XmlPullParser vendorInput = getApplicationContext().getResources().getXml(R.xml.vendor); 134 try { 135 PersistableBundle vendorConfig = readConfigFromXml(vendorInput, id); 136 config.putAll(vendorConfig); 137 } 138 catch (IOException | XmlPullParserException e) { 139 Log.e(TAG, e.toString()); 140 } 141 142 return config; 143 } 144 145 /** 146 * Parses an XML document and returns a PersistableBundle. 147 * 148 * <p>This function iterates over each {@code <carrier_config>} node in the XML document and 149 * parses it into a bundle if its filters match {@code id}. XML documents named after carrier id 150 * doesn't support filter match as each carrier including MVNOs will have its own config file. 151 * The format of XML bundles is defined 152 * by {@link PersistableBundle#restoreFromXml}. All the matching bundles will be flattened and 153 * returned as a single bundle.</p> 154 * 155 * <p>Here is an example document in vendor.xml. 156 * <pre>{@code 157 * <carrier_config_list> 158 * <carrier_config cid="1938" name="verizon"> 159 * <boolean name="voicemail_notification_persistent_bool" value="true" /> 160 * </carrier_config> 161 * <carrier_config cid="1788" name="sprint"> 162 * <boolean name="voicemail_notification_persistent_bool" value="false" /> 163 * </carrier_config> 164 * </carrier_config_list> 165 * }</pre></p> 166 * 167 * <p>Here is an example document. The second bundle will be applied to the first only if the 168 * GID1 is ABCD. 169 * <pre>{@code 170 * <carrier_config_list> 171 * <carrier_config> 172 * <boolean name="voicemail_notification_persistent_bool" value="true" /> 173 * </carrier_config> 174 * <carrier_config gid1="ABCD"> 175 * <boolean name="voicemail_notification_persistent_bool" value="false" /> 176 * </carrier_config> 177 * </carrier_config_list> 178 * }</pre></p> 179 * 180 * @param parser an XmlPullParser pointing at the beginning of the document. 181 * @param id the details of the SIM operator used to filter parts of the document. If read from 182 * files named after carrier id, this will be set to {@null code} as no filter match 183 * needed. 184 * @return a possibly empty PersistableBundle containing the config values. 185 */ readConfigFromXml(XmlPullParser parser, @Nullable CarrierIdentifier id)186 static PersistableBundle readConfigFromXml(XmlPullParser parser, @Nullable CarrierIdentifier id) 187 throws IOException, XmlPullParserException { 188 PersistableBundle config = new PersistableBundle(); 189 190 if (parser == null) { 191 return config; 192 } 193 194 // Iterate over each <carrier_config> node in the document and add it to the returned 195 // bundle if its filters match. 196 int event; 197 while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) { 198 if (event == XmlPullParser.START_TAG && "carrier_config".equals(parser.getName())) { 199 // Skip this fragment if it has filters that don't match. 200 if (id != null && !checkFilters(parser, id)) { 201 continue; 202 } 203 PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser); 204 config.putAll(configFragment); 205 } 206 } 207 208 return config; 209 } 210 211 /** 212 * Checks to see if an XML node matches carrier filters. 213 * 214 * <p>This iterates over the attributes of the current tag pointed to by {@code parser} and 215 * checks each one against {@code id} or {@link Build.DEVICE}. Attributes that are not specified 216 * in the node will not be checked, so a node with no attributes will always return true. The 217 * supported filter attributes are, 218 * <ul> 219 * <li>mcc: {@link CarrierIdentifier#getMcc}</li> 220 * <li>mnc: {@link CarrierIdentifier#getMnc}</li> 221 * <li>gid1: {@link CarrierIdentifier#getGid1}</li> 222 * <li>gid2: {@link CarrierIdentifier#getGid2}</li> 223 * <li>spn: {@link CarrierIdentifier#getSpn}</li> 224 * <li>imsi: {@link CarrierIdentifier#getImsi}</li> 225 * <li>device: {@link Build.DEVICE}</li> 226 * <li>cid: {@link CarrierIdentifier#getCarrierId()} 227 * or {@link CarrierIdentifier#getSpecificCarrierId()}</li> 228 * </ul> 229 * </p> 230 * 231 * <p> 232 * The attributes imsi and spn can be expressed as regexp to filter on patterns. 233 * The spn attribute can be set to the string "null" to allow matching against a SIM 234 * with no spn set. 235 * </p> 236 * 237 * @param parser an XmlPullParser pointing at a START_TAG with the attributes to check. 238 * @param id the carrier details to check against. 239 * @return false if any XML attribute does not match the corresponding value. 240 */ checkFilters(XmlPullParser parser, CarrierIdentifier id)241 static boolean checkFilters(XmlPullParser parser, CarrierIdentifier id) { 242 boolean result = true; 243 for (int i = 0; i < parser.getAttributeCount(); ++i) { 244 String attribute = parser.getAttributeName(i); 245 String value = parser.getAttributeValue(i); 246 switch (attribute) { 247 case "mcc": 248 result = result && value.equals(id.getMcc()); 249 break; 250 case "mnc": 251 result = result && value.equals(id.getMnc()); 252 break; 253 case "gid1": 254 result = result && value.equalsIgnoreCase(id.getGid1()); 255 break; 256 case "gid2": 257 result = result && value.equalsIgnoreCase(id.getGid2()); 258 break; 259 case "spn": 260 result = result && matchOnSP(value, id); 261 break; 262 case "imsi": 263 result = result && matchOnImsi(value, id); 264 break; 265 case "device": 266 result = result && value.equalsIgnoreCase(Build.DEVICE); 267 break; 268 case "cid": 269 result = result && ((Integer.parseInt(value) == id.getCarrierId()) 270 || (Integer.parseInt(value) == id.getSpecificCarrierId())); 271 break; 272 case "name": 273 // name is used together with cid for readability. ignore for filter. 274 break; 275 default: 276 Log.e(TAG, "Unknown attribute " + attribute + "=" + value); 277 result = false; 278 break; 279 } 280 } 281 return result; 282 } 283 284 /** 285 * Check to see if the IMSI expression from the XML matches the IMSI of the 286 * Carrier. 287 * 288 * @param xmlImsi IMSI expression fetched from the resource XML 289 * @param id Id of the evaluated CarrierIdentifier 290 * @return true if the XML IMSI matches the IMSI of CarrierIdentifier, false 291 * otherwise. 292 */ matchOnImsi(String xmlImsi, CarrierIdentifier id)293 static boolean matchOnImsi(String xmlImsi, CarrierIdentifier id) { 294 boolean matchFound = false; 295 296 String currentImsi = id.getImsi(); 297 // If we were able to retrieve current IMSI, see if it matches. 298 if (currentImsi != null) { 299 Pattern imsiPattern = Pattern.compile(xmlImsi, Pattern.CASE_INSENSITIVE); 300 Matcher matcher = imsiPattern.matcher(currentImsi); 301 matchFound = matcher.matches(); 302 } 303 return matchFound; 304 } 305 306 /** 307 * Check to see if the service provider name expression from the XML matches the 308 * CarrierIdentifier. 309 * 310 * @param xmlSP SP expression fetched from the resource XML 311 * @param id Id of the evaluated CarrierIdentifier 312 * @return true if the XML SP matches the phone's SP, false otherwise. 313 */ matchOnSP(String xmlSP, CarrierIdentifier id)314 static boolean matchOnSP(String xmlSP, CarrierIdentifier id) { 315 boolean matchFound = false; 316 317 String currentSP = id.getSpn(); 318 if (SPN_EMPTY_MATCH.equalsIgnoreCase(xmlSP)) { 319 if (TextUtils.isEmpty(currentSP)) { 320 matchFound = true; 321 } 322 } else if (currentSP != null) { 323 Pattern spPattern = Pattern.compile(xmlSP, Pattern.CASE_INSENSITIVE); 324 Matcher matcher = spPattern.matcher(currentSP); 325 matchFound = matcher.matches(); 326 } 327 return matchFound; 328 } 329 } 330