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