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