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