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