1 /* 2 * Copyright (C) 2020 Google LLC 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 package com.google.carrier; 17 18 import static com.google.common.collect.Multimaps.flatteningToMultimap; 19 import static com.google.common.collect.Multimaps.toMultimap; 20 import static java.nio.charset.StandardCharsets.UTF_8; 21 import static java.util.Comparator.comparing; 22 23 import com.beust.jcommander.JCommander; 24 import com.beust.jcommander.Parameter; 25 import com.beust.jcommander.Parameters; 26 import com.google.auto.value.AutoValue; 27 import com.google.common.base.Ascii; 28 import com.google.common.base.CharMatcher; 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.collect.Multimap; 33 import com.google.common.collect.MultimapBuilder; 34 import com.google.protobuf.Descriptors; 35 import com.google.protobuf.TextFormat; 36 import com.google.carrier.CarrierConfig; 37 import com.google.carrier.CarrierId; 38 import com.google.carrier.CarrierList; 39 import com.google.carrier.CarrierMap; 40 import com.google.carrier.CarrierSettings; 41 import com.google.carrier.IntArray; 42 import com.google.carrier.MultiCarrierSettings; 43 import com.google.carrier.TextArray; 44 import com.android.providers.telephony.CarrierIdProto.CarrierAttribute; 45 import java.io.BufferedReader; 46 import java.io.BufferedWriter; 47 import java.io.File; 48 import java.io.FileInputStream; 49 import java.io.FileOutputStream; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.io.InputStreamReader; 53 import java.io.OutputStream; 54 import java.io.OutputStreamWriter; 55 import java.util.ArrayList; 56 import java.util.HashMap; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.TreeMap; 60 import java.util.regex.Matcher; 61 import java.util.regex.Pattern; 62 import javax.xml.parsers.DocumentBuilder; 63 import javax.xml.parsers.DocumentBuilderFactory; 64 import javax.xml.parsers.ParserConfigurationException; 65 import org.w3c.dom.Document; 66 import org.w3c.dom.Element; 67 import org.w3c.dom.NamedNodeMap; 68 import org.w3c.dom.Node; 69 import org.w3c.dom.NodeList; 70 import org.xml.sax.SAXException; 71 72 /** 73 * This command converts carrier config XML into text protobuf. 74 * 75 * <ul> 76 * <li>input: the assets/ from AOSP CarrierConfig app 77 * <li>input: vendor.xml file(s) which override(s) assets 78 * <li>input: a tier-1 carrier list in text protobuf (in --output_dir) 79 * <li>input: the version number for output files 80 * <li>output: an other_carriers.textpb - a list of other (non-tier-1) carriers 81 * <li>output: an others.textpb containing carrier configs for non tier-1 carriers 82 * <li>output: a .textpb for every single tier-1 carrier 83 * </ul> 84 */ 85 @Parameters(separators = "=") 86 public final class CarrierConfigConverterV2 { 87 @Parameter(names = "--assets", description = "The source AOSP assets/ directory.") 88 private String assetsDirName = "/tmp/carrierconfig/assets"; 89 90 @Parameter( 91 names = "--vendor_xml", 92 description = 93 "The source vendor.xml file(s). If multiple files provided, the order decides config" 94 + " precedence, ie. configs in a file are overwritten by configs in files AFTER it.") 95 private List<String> vendorXmlFiles = ImmutableList.of("/tmp/carrierconfig/vendor.xml"); 96 97 @Parameter( 98 names = "--output_dir", 99 description = "The destination data directory, with tier1_carriers.textpb in it.") 100 private String outputDir = "/tmp/carrierconfig/out"; 101 102 @Parameter(names = "--version", description = "The version number for all output textpb.") 103 private long version = 1L; 104 105 private static final String MCCMNC_FOR_DEFAULT_SETTINGS = "000000"; 106 107 // Resource file path to the AOSP carrier list file 108 private static final String RESOURCE_CARRIER_LIST = 109 "/assets/latest_carrier_id/carrier_list.textpb"; 110 111 // Constants used in parsing XMLs. 112 private static final String XML_SUFFIX = ".xml"; 113 private static final String CARRIER_CONFIG_MCCMNC_XML_PREFIX = "carrier_config_mccmnc_"; 114 private static final String CARRIER_CONFIG_CID_XML_PREFIX = "carrier_config_carrierid_"; 115 private static final String KEY_MCCMNC_PREFIX = "mccmnc_"; 116 private static final String KEY_CID_PREFIX = "cid_"; 117 private static final String TAG_CARRIER_CONFIG = "carrier_config"; 118 119 /** Entry point when invoked from command line. */ main(String[] args)120 public static void main(String[] args) throws IOException { 121 CarrierConfigConverterV2 converter = new CarrierConfigConverterV2(); 122 new JCommander(converter, args); 123 converter.convert(); 124 } 125 126 /** Entry point when invoked from other Java code, eg. the server side conversion tool. */ convert( String vendorXmlFile, String assetsDirName, String outputDir, long version)127 public static void convert( 128 String vendorXmlFile, String assetsDirName, String outputDir, long version) 129 throws IOException { 130 CarrierConfigConverterV2 converter = new CarrierConfigConverterV2(); 131 converter.vendorXmlFiles = ImmutableList.of(vendorXmlFile); 132 converter.assetsDirName = assetsDirName; 133 converter.outputDir = outputDir; 134 converter.version = version; 135 converter.convert(); 136 } 137 convert()138 private void convert() throws IOException { 139 String carriersTextpbFile = getPathAsString(outputDir, "tier1_carriers.textpb"); 140 String settingsTextpbDir = getPathAsString(outputDir, "setting"); 141 CarrierList tier1Carriers; 142 ArrayList<CarrierMap> otherCarriers = new ArrayList<>(); 143 ArrayList<String> outFiles = new ArrayList<>(); 144 HashMap<CarrierId, Map<String, CarrierConfig.Config>> rawConfigs = new HashMap<>(); 145 TreeMap<String, CarrierConfig> tier1Configs = new TreeMap<>(); 146 TreeMap<String, CarrierConfig> othersConfigs = new TreeMap<>(); 147 DocumentBuilder xmlDocBuilder = getDocumentBuilder(); 148 Multimap<Integer, CarrierId> aospCarrierList = loadAospCarrierList(); 149 Multimap<CarrierId, Integer> reverseAospCarrierList = reverseAospCarrierList(aospCarrierList); 150 151 /* 152 * High-level flow: 153 * 1. Parse all input XMLs into memory 154 * 2. Collect a list of interested carriers from input, represented by CarrierId. 155 * 2. For each CarrierId, build its carreir configs, following AOSP DefaultCarrierConfigService. 156 * 3. Merge CarrierId's as per tier1_carriers.textpb 157 */ 158 159 // 1. Parse all input XMLs into memory 160 Map<String, Document> assetsXmls = new HashMap<>(); 161 List<Document> vendorXmls = new ArrayList<>(); 162 // Parse assets/carrier_config_*.xml 163 for (File childFile : new File(assetsDirName).listFiles()) { 164 String childFileName = childFile.getName(); 165 String fullChildName = childFile.getCanonicalPath(); 166 if (childFileName.startsWith(CARRIER_CONFIG_MCCMNC_XML_PREFIX)) { 167 String mccMnc = 168 childFileName.substring( 169 CARRIER_CONFIG_MCCMNC_XML_PREFIX.length(), childFileName.indexOf(XML_SUFFIX)); 170 if (!mccMnc.matches("\\d{5,6}")) { 171 throw new IOException("Invalid mcc/mnc " + mccMnc + " found in " + childFileName); 172 } 173 try { 174 assetsXmls.put(KEY_MCCMNC_PREFIX + mccMnc, parseXmlDoc(fullChildName, xmlDocBuilder)); 175 } catch (SAXException | IOException e) { 176 throw new IOException("Failed to parse " + childFileName, e); 177 } 178 } else if (childFileName.startsWith(CARRIER_CONFIG_CID_XML_PREFIX)) { 179 String cidAndCarrierName = 180 childFileName.substring( 181 CARRIER_CONFIG_CID_XML_PREFIX.length(), childFileName.indexOf(XML_SUFFIX)); 182 int cid = -1; 183 try { 184 cid = Integer.parseInt(cidAndCarrierName.split("_", -1)[0]); 185 } catch (NumberFormatException e) { 186 throw new IOException("Invalid carrierid found in " + childFileName, e); 187 } 188 try { 189 assetsXmls.put(KEY_CID_PREFIX + cid, parseXmlDoc(fullChildName, xmlDocBuilder)); 190 } catch (SAXException | IOException e) { 191 throw new IOException("Failed to parse " + childFileName, e); 192 } 193 } 194 // ignore other malformatted files. 195 } 196 // Parse vendor.xml files 197 for (String vendorXmlFile : vendorXmlFiles) { 198 try { 199 vendorXmls.add(parseXmlDoc(vendorXmlFile, xmlDocBuilder)); 200 } catch (SAXException | IOException e) { 201 throw new IOException("Failed to parse " + vendorXmlFile, e); 202 } 203 } 204 205 // 2. Collect all carriers from input, represented by CarrierId. 206 List<CarrierId> carriers = new ArrayList<>(); 207 // Traverse <carrier_config /> labels in each file. 208 for (Map.Entry<String, Document> xml : assetsXmls.entrySet()) { 209 if (xml.getKey().startsWith(KEY_MCCMNC_PREFIX)) { 210 String mccMnc = xml.getKey().substring(KEY_MCCMNC_PREFIX.length()); 211 for (Element element : getElementsByTagName(xml.getValue(), TAG_CARRIER_CONFIG)) { 212 try { 213 CarrierId id = parseCarrierId(element).setMccMnc(mccMnc).build(); 214 carriers.add(id); 215 } catch (UnsupportedOperationException e) { 216 throw new IOException("Unsupported syntax in assets/ for " + mccMnc, e); 217 } 218 } 219 } else if (xml.getKey().startsWith(KEY_CID_PREFIX)) { 220 int cid = Integer.parseInt(xml.getKey().substring(KEY_CID_PREFIX.length())); 221 if (aospCarrierList.containsKey(cid)) { 222 carriers.addAll(aospCarrierList.get(cid)); 223 } else { 224 System.err.printf("Undefined cid %d in assets/. Ignore.\n", cid); 225 } 226 } 227 } 228 for (Document vendorXml : vendorXmls) { 229 for (Element element : getElementsByTagName(vendorXml, TAG_CARRIER_CONFIG)) { 230 // First, try to parse cid 231 if (element.hasAttribute("cid")) { 232 String cidAsString = element.getAttribute("cid"); 233 int cid = Integer.parseInt(cidAsString); 234 if (aospCarrierList.containsKey(cid)) { 235 carriers.addAll(aospCarrierList.get(cid)); 236 } else { 237 System.err.printf("Undefined cid %d in vendor.xml. Ignore.\n", cid); 238 } 239 } else { 240 // Then, try to parse CarrierId 241 CarrierId.Builder id = parseCarrierId(element); 242 // A valid mccmnc is 5- or 6-digit. But vendor.xml see special cases below: 243 // Case 1: a <carrier_config> element may have neither "mcc" nor "mnc". 244 // Such a tag provides configs that should be applied to all carriers, including to 245 // unspecified carriers via the 000/000 default configs. Make sure 000/000 exists as 246 // a carrier. 247 // Case 2: a <carrier_config> element may have just "mcc" and not "mnc" for 248 // country-wise config. Such a element doesn't make a carrier; but still keep it so 249 // can be used if a mccmnc appears in APNs later. 250 if (id.getMccMnc().isEmpty()) { 251 // special case 1 252 carriers.add(id.setMccMnc(MCCMNC_FOR_DEFAULT_SETTINGS).build()); 253 } else if (id.getMccMnc().length() == 3) { 254 // special case 2 255 carriers.add(id.build()); 256 } else if (id.getMccMnc().length() == 5 || id.getMccMnc().length() == 6) { 257 // Normal mcc+mnc 258 carriers.add(id.build()); 259 } else { 260 System.err.printf("Invalid mcc/mnc: %s. Ignore.\n", id.getMccMnc()); 261 } 262 } 263 } 264 } 265 266 // 3. For each CarrierId, build its carrier configs, following AOSP DefaultCarrierConfigService. 267 for (CarrierId carrier : carriers) { 268 Map<String, CarrierConfig.Config> config = ImmutableMap.of(); 269 270 CarrierIdentifier id = getCid(carrier, reverseAospCarrierList); 271 if (id.getCarrierId() != -1) { 272 HashMap<String, CarrierConfig.Config> configBySpecificCarrierId = 273 parseCarrierConfigFromXml( 274 assetsXmls.get(KEY_CID_PREFIX + id.getSpecificCarrierId()), id); 275 HashMap<String, CarrierConfig.Config> configByCarrierId = 276 parseCarrierConfigFromXml(assetsXmls.get(KEY_CID_PREFIX + id.getCarrierId()), id); 277 HashMap<String, CarrierConfig.Config> configByMccMncFallBackCarrierId = 278 parseCarrierConfigFromXml(assetsXmls.get(KEY_CID_PREFIX + id.getMccmncCarrierId()), id); 279 // priority: specific carrier id > carrier id > mccmnc fallback carrier id 280 if (!configBySpecificCarrierId.isEmpty()) { 281 config = configBySpecificCarrierId; 282 } else if (!configByCarrierId.isEmpty()) { 283 config = configByCarrierId; 284 } else if (!configByMccMncFallBackCarrierId.isEmpty()) { 285 config = configByMccMncFallBackCarrierId; 286 } 287 } 288 if (config.isEmpty()) { 289 // fallback to use mccmnc.xml when there is no carrier id named configuration found. 290 config = 291 parseCarrierConfigFromXml(assetsXmls.get(KEY_MCCMNC_PREFIX + carrier.getMccMnc()), id); 292 } 293 // Treat vendor.xml files as if they were appended to the carrier configs read from assets. 294 for (Document vendorXml : vendorXmls) { 295 HashMap<String, CarrierConfig.Config> vendorConfig = 296 parseCarrierConfigFromVendorXml(vendorXml, id); 297 config.putAll(vendorConfig); 298 } 299 300 rawConfigs.put(carrier, config); 301 } 302 303 // Read tier1_carriers.textpb 304 try (InputStream carriersTextpb = new FileInputStream(new File(carriersTextpbFile)); 305 BufferedReader br = new BufferedReader(new InputStreamReader(carriersTextpb, UTF_8))) { 306 CarrierList.Builder builder = CarrierList.newBuilder(); 307 TextFormat.getParser().merge(br, builder); 308 tier1Carriers = builder.build(); 309 } 310 311 // Compose tier1Configs and othersConfigs from rawConfigs 312 rawConfigs.forEach( 313 (carrierId, configs) -> { 314 String cname = getCanonicalName(tier1Carriers, carrierId); 315 CarrierConfig.Builder ccb = toCarrierConfigBuilder(configs); 316 if (cname != null) { // tier-1 carrier 317 if (tier1Configs.containsKey(cname)) { 318 tier1Configs.put( 319 cname, CarrierProtoUtils.mergeCarrierConfig(tier1Configs.get(cname), ccb)); 320 } else { 321 tier1Configs.put(cname, ccb.build()); 322 } 323 } else { // other carrier 324 cname = generateCanonicalNameForOthers(carrierId); 325 otherCarriers.add( 326 CarrierMap.newBuilder().addCarrierId(carrierId).setCanonicalName(cname).build()); 327 othersConfigs.put(cname, ccb.build()); 328 } 329 }); 330 331 // output tier1 carrier settings 332 for (int i = 0; i < tier1Carriers.getEntryCount(); i++) { 333 CarrierMap cm = tier1Carriers.getEntry(i); 334 String cname = cm.getCanonicalName(); 335 String fileName = getPathAsString(settingsTextpbDir, cname + ".textpb"); 336 337 outFiles.add(fileName); 338 339 try (OutputStream os = new FileOutputStream(new File(fileName)); 340 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, UTF_8))) { 341 CarrierSettings.Builder cs = CarrierSettings.newBuilder().setCanonicalName(cname); 342 if (tier1Configs.containsKey(cname)) { 343 cs.setConfigs(sortConfig(tier1Configs.get(cname)).toBuilder().build()); 344 } 345 cs.setVersion(version); 346 TextFormat.printUnicode(cs.build(), bw); 347 } 348 } 349 350 // Output other carriers list 351 String otherCarriersFile = getPathAsString(outputDir, "other_carriers.textpb"); 352 outFiles.add(otherCarriersFile); 353 CarrierProtoUtils.sortCarrierMapEntries(otherCarriers); 354 try (OutputStream os = new FileOutputStream(new File(otherCarriersFile)); 355 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, UTF_8))) { 356 CarrierList cl = 357 CarrierList.newBuilder().addAllEntry(otherCarriers).setVersion(version).build(); 358 TextFormat.printUnicode(cl, bw); 359 } 360 361 // Output other carriers settings 362 String othersFileName = getPathAsString(settingsTextpbDir, "others.textpb"); 363 outFiles.add(othersFileName); 364 try (OutputStream os = new FileOutputStream(new File(othersFileName)); 365 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, UTF_8))) { 366 MultiCarrierSettings.Builder mcs = MultiCarrierSettings.newBuilder().setVersion(version); 367 othersConfigs.forEach( 368 (cname, cc) -> { 369 mcs.addSetting( 370 CarrierSettings.newBuilder() 371 .setCanonicalName(cname) 372 .setConfigs(sortConfig(cc).toBuilder().build()) 373 .build()); 374 }); 375 TextFormat.printUnicode(mcs.build(), bw); 376 } 377 378 // Print out the list of all output file names 379 System.out.println("SUCCESS! Files generated:"); 380 for (String fileName : outFiles) { 381 System.out.println(fileName); 382 } 383 } 384 getDocumentBuilder()385 private static DocumentBuilder getDocumentBuilder() { 386 try { 387 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); 388 return dbFactory.newDocumentBuilder(); 389 } catch (ParserConfigurationException e) { 390 throw new IllegalStateException(e); 391 } 392 } 393 loadAospCarrierList()394 private static Multimap<Integer, CarrierId> loadAospCarrierList() throws IOException { 395 com.android.providers.telephony.CarrierIdProto.CarrierList.Builder aospCarrierList = 396 com.android.providers.telephony.CarrierIdProto.CarrierList.newBuilder(); 397 try (InputStream textpb = 398 CarrierConfigConverterV2.class.getResourceAsStream(RESOURCE_CARRIER_LIST); 399 BufferedReader textpbReader = new BufferedReader(new InputStreamReader(textpb, UTF_8))) { 400 TextFormat.getParser().merge(textpbReader, aospCarrierList); 401 } 402 return aospCarrierList.getCarrierIdList().stream() 403 .collect( 404 flatteningToMultimap( 405 cid -> cid.getCanonicalId(), 406 cid -> carrierAttributeToCarrierId(cid.getCarrierAttributeList()).stream(), 407 MultimapBuilder.linkedHashKeys().arrayListValues()::build)); 408 } 409 410 // Convert `CarrierAttribute`s to `CarrierId`s. 411 // A CarrierAttribute message with fields not supported by CarrierSettings, like preferred_apn, 412 // is ignored. carrierAttributeToCarrierId( List<CarrierAttribute> carrierAttributes)413 private static ImmutableList<CarrierId> carrierAttributeToCarrierId( 414 List<CarrierAttribute> carrierAttributes) { 415 List<CarrierId> result = new ArrayList<>(); 416 ImmutableSet<Descriptors.FieldDescriptor> supportedFields = 417 ImmutableSet.of( 418 CarrierAttribute.getDescriptor().findFieldByName("mccmnc_tuple"), 419 CarrierAttribute.getDescriptor().findFieldByName("imsi_prefix_xpattern"), 420 CarrierAttribute.getDescriptor().findFieldByName("spn"), 421 CarrierAttribute.getDescriptor().findFieldByName("gid1")); 422 for (CarrierAttribute carrierAttribute : carrierAttributes) { 423 if (!carrierAttribute.getAllFields().keySet().stream().allMatch(supportedFields::contains)) { 424 // This `CarrierAttribute` contains unsupported fields; skip. 425 continue; 426 } 427 for (String mccmnc : carrierAttribute.getMccmncTupleList()) { 428 CarrierId.Builder carrierId = CarrierId.newBuilder().setMccMnc(mccmnc); 429 if (carrierAttribute.getImsiPrefixXpatternCount() > 0) { 430 for (String imsi : carrierAttribute.getImsiPrefixXpatternList()) { 431 result.add(carrierId.setImsi(imsi).build()); 432 } 433 } else if (carrierAttribute.getGid1Count() > 0) { 434 for (String gid1 : carrierAttribute.getGid1List()) { 435 result.add(carrierId.setGid1(gid1).build()); 436 } 437 } else if (carrierAttribute.getSpnCount() > 0) { 438 for (String spn : carrierAttribute.getSpnList()) { 439 // Some SPN has trailng space character \r, messing up textpb. Remove them. 440 // It won't affect CarrierSettings which uses prefix matching for SPN. 441 result.add(carrierId.setSpn(CharMatcher.whitespace().trimTrailingFrom(spn)).build()); 442 } 443 } else { // Ignore other attributes not supported by CarrierSettings 444 result.add(carrierId.build()); 445 } 446 } 447 } 448 // Dedup 449 return ImmutableSet.copyOf(result).asList(); 450 } 451 reverseAospCarrierList( Multimap<Integer, CarrierId> aospCarrierList)452 private static Multimap<CarrierId, Integer> reverseAospCarrierList( 453 Multimap<Integer, CarrierId> aospCarrierList) { 454 return aospCarrierList.entries().stream() 455 .collect( 456 toMultimap( 457 entry -> entry.getValue(), 458 entry -> entry.getKey(), 459 MultimapBuilder.linkedHashKeys().arrayListValues()::build)); 460 } 461 parseXmlDoc(String fileName, DocumentBuilder xmlDocBuilder)462 private static Document parseXmlDoc(String fileName, DocumentBuilder xmlDocBuilder) 463 throws SAXException, IOException { 464 try (InputStream configXml = new FileInputStream(new File(fileName))) { 465 Document xmlDoc = xmlDocBuilder.parse(configXml); 466 xmlDoc.getDocumentElement().normalize(); 467 return xmlDoc; 468 } 469 } 470 getElementsByTagName(Document xmlDoc, String tagName)471 private static ImmutableList<Element> getElementsByTagName(Document xmlDoc, String tagName) { 472 if (xmlDoc == null) { 473 return ImmutableList.of(); 474 } 475 ImmutableList.Builder<Element> result = new ImmutableList.Builder<>(); 476 xmlDoc.getDocumentElement().normalize(); 477 NodeList nodeList = xmlDoc.getElementsByTagName(tagName); 478 for (int i = 0; i < nodeList.getLength(); i++) { 479 Node node = nodeList.item(i); 480 if (node.getNodeType() == Node.ELEMENT_NODE) { 481 result.add((Element) node); 482 } 483 } 484 return result.build(); 485 } 486 sortConfig(CarrierConfig in)487 static CarrierConfig sortConfig(CarrierConfig in) { 488 final CarrierConfig.Builder result = in.toBuilder().clearConfig(); 489 in.getConfigList().stream() 490 .sorted(comparing(CarrierConfig.Config::getKey)) 491 .forEachOrdered((c) -> result.addConfig(c)); 492 return result.build(); 493 } 494 getCanonicalName(CarrierList pList, CarrierId pId)495 static String getCanonicalName(CarrierList pList, CarrierId pId) { 496 for (int i = 0; i < pList.getEntryCount(); i++) { 497 CarrierMap cm = pList.getEntry(i); 498 for (int j = 0; j < cm.getCarrierIdCount(); j++) { 499 CarrierId cid = cm.getCarrierId(j); 500 if (cid.equals(pId)) { 501 return cm.getCanonicalName(); 502 } 503 } 504 } 505 return null; 506 } 507 generateCanonicalNameForOthers(CarrierId pId)508 static String generateCanonicalNameForOthers(CarrierId pId) { 509 // Not a tier-1 carrier: generate name 510 StringBuilder genName = new StringBuilder(pId.getMccMnc()); 511 switch (pId.getMvnoDataCase()) { 512 case GID1: 513 genName.append("GID1="); 514 genName.append(Ascii.toUpperCase(pId.getGid1())); 515 break; 516 case SPN: 517 genName.append("SPN="); 518 genName.append(Ascii.toUpperCase(pId.getSpn())); 519 break; 520 case IMSI: 521 genName.append("IMSI="); 522 genName.append(Ascii.toUpperCase(pId.getImsi())); 523 break; 524 default: // MVNODATA_NOT_SET 525 // Do nothing 526 } 527 return genName.toString(); 528 } 529 530 /** 531 * Converts a map with carrier configs to a {@link CarrierConfig.Builder}. 532 * 533 * @see #parseCarrierConfigToMap 534 */ toCarrierConfigBuilder( Map<String, CarrierConfig.Config> configs)535 private static CarrierConfig.Builder toCarrierConfigBuilder( 536 Map<String, CarrierConfig.Config> configs) { 537 CarrierConfig.Builder builder = CarrierConfig.newBuilder(); 538 configs.forEach( 539 (key, value) -> { 540 builder.addConfig(value.toBuilder().setKey(key)); 541 }); 542 return builder; 543 } 544 545 /** 546 * Returns a map with carrier configs parsed from a assets/*.xml. 547 * 548 * @return a map, key being the carrier config key, value being a {@link CarrierConfig.Config} 549 * with one of the value set. 550 */ parseCarrierConfigFromXml( Document xmlDoc, CarrierIdentifier carrier)551 private static HashMap<String, CarrierConfig.Config> parseCarrierConfigFromXml( 552 Document xmlDoc, CarrierIdentifier carrier) throws IOException { 553 HashMap<String, CarrierConfig.Config> configMap = new HashMap<>(); 554 for (Element element : getElementsByTagName(xmlDoc, TAG_CARRIER_CONFIG)) { 555 if (carrier != null && !checkFilters(element, carrier)) { 556 continue; 557 } 558 configMap.putAll(parseCarrierConfigToMap(element)); 559 } 560 return configMap; 561 } 562 563 /** 564 * Returns a map with carrier configs parsed from the vendor.xml. 565 * 566 * @return a map, key being the carrier config key, value being a {@link CarrierConfig.Config} 567 * with one of the value set. 568 */ parseCarrierConfigFromVendorXml( Document xmlDoc, CarrierIdentifier carrier)569 private static HashMap<String, CarrierConfig.Config> parseCarrierConfigFromVendorXml( 570 Document xmlDoc, CarrierIdentifier carrier) throws IOException { 571 HashMap<String, CarrierConfig.Config> configMap = new HashMap<>(); 572 for (Element element : getElementsByTagName(xmlDoc, TAG_CARRIER_CONFIG)) { 573 if (carrier != null && !checkFilters(element, carrier)) { 574 continue; 575 } 576 configMap.putAll(parseCarrierConfigToMap(element)); 577 } 578 return configMap; 579 } 580 581 /** 582 * Returns a map with carrier configs parsed from the XML element. 583 * 584 * @return a map, key being the carrier config key, value being a {@link CarrierConfig.Config} 585 * with one of the value set. 586 */ parseCarrierConfigToMap(Element element)587 private static HashMap<String, CarrierConfig.Config> parseCarrierConfigToMap(Element element) 588 throws IOException { 589 HashMap<String, CarrierConfig.Config> configMap = new HashMap<>(); 590 NodeList nList; 591 // bool value 592 nList = element.getElementsByTagName("boolean"); 593 for (int i = 0; i < nList.getLength(); i++) { 594 Node nNode = nList.item(i); 595 if (nNode.getNodeType() != Node.ELEMENT_NODE) { 596 continue; 597 } 598 Element eElement = (Element) nNode; 599 String key = eElement.getAttribute("name"); 600 boolean value = Boolean.parseBoolean(eElement.getAttribute("value")); 601 configMap.put(key, CarrierConfig.Config.newBuilder().setBoolValue(value).build()); 602 } 603 // int value 604 nList = element.getElementsByTagName("int"); 605 for (int i = 0; i < nList.getLength(); i++) { 606 Node nNode = nList.item(i); 607 if (nNode.getNodeType() != Node.ELEMENT_NODE) { 608 continue; 609 } 610 Element eElement = (Element) nNode; 611 String key = eElement.getAttribute("name"); 612 int value = Integer.parseInt(eElement.getAttribute("value")); 613 configMap.put(key, CarrierConfig.Config.newBuilder().setIntValue(value).build()); 614 } 615 // long value 616 nList = element.getElementsByTagName("long"); 617 for (int i = 0; i < nList.getLength(); i++) { 618 Node nNode = nList.item(i); 619 if (nNode.getNodeType() != Node.ELEMENT_NODE) { 620 continue; 621 } 622 Element eElement = (Element) nNode; 623 String key = eElement.getAttribute("name"); 624 long value = Long.parseLong(eElement.getAttribute("value")); 625 configMap.put(key, CarrierConfig.Config.newBuilder().setLongValue(value).build()); 626 } 627 // text value 628 nList = element.getElementsByTagName("string"); 629 for (int i = 0; i < nList.getLength(); i++) { 630 Node nNode = nList.item(i); 631 if (nNode.getNodeType() != Node.ELEMENT_NODE) { 632 continue; 633 } 634 Element eElement = (Element) nNode; 635 String key = eElement.getAttribute("name"); 636 String value = String.valueOf(eElement.getTextContent()); 637 if (value.isEmpty()) { 638 value = eElement.getAttribute("value"); 639 } 640 configMap.put(key, CarrierConfig.Config.newBuilder().setTextValue(value).build()); 641 } 642 // text array 643 nList = element.getElementsByTagName("string-array"); 644 for (int i = 0; i < nList.getLength(); i++) { 645 Node nNode = nList.item(i); 646 if (nNode.getNodeType() != Node.ELEMENT_NODE) { 647 continue; 648 } 649 Element eElement = (Element) nNode; 650 String key = eElement.getAttribute("name"); 651 CarrierConfig.Config.Builder cccb = CarrierConfig.Config.newBuilder(); 652 TextArray.Builder cctb = TextArray.newBuilder(); 653 NodeList subList = eElement.getElementsByTagName("item"); 654 for (int j = 0; j < subList.getLength(); j++) { 655 Node subNode = subList.item(j); 656 if (subNode.getNodeType() != Node.ELEMENT_NODE) { 657 continue; 658 } 659 Element subElement = (Element) subNode; 660 String value = String.valueOf(subElement.getAttribute("value")); 661 cctb.addItem(value); 662 } 663 configMap.put(key, cccb.setTextArray(cctb.build()).build()); 664 } 665 // bool array 666 nList = element.getElementsByTagName("int-array"); 667 for (int i = 0; i < nList.getLength(); i++) { 668 Node nNode = nList.item(i); 669 if (nNode.getNodeType() != Node.ELEMENT_NODE) { 670 continue; 671 } 672 Element eElement = (Element) nNode; 673 String key = eElement.getAttribute("name"); 674 CarrierConfig.Config.Builder cccb = CarrierConfig.Config.newBuilder(); 675 IntArray.Builder ccib = IntArray.newBuilder(); 676 NodeList subList = eElement.getElementsByTagName("item"); 677 for (int j = 0; j < subList.getLength(); j++) { 678 Node subNode = subList.item(j); 679 if (subNode.getNodeType() != Node.ELEMENT_NODE) { 680 continue; 681 } 682 Element subElement = (Element) subNode; 683 int value = Integer.parseInt(subElement.getAttribute("value")); 684 ccib.addItem(value); 685 } 686 configMap.put(key, cccb.setIntArray(ccib.build()).build()); 687 } 688 return configMap; 689 } 690 691 /** 692 * Returns {@code true} if a <carrier_config ...> element matches the carrier identifier. 693 * 694 * <p>Copied from AOSP DefaultCarrierConfigService. 695 */ checkFilters(Element element, CarrierIdentifier id)696 private static boolean checkFilters(Element element, CarrierIdentifier id) { 697 boolean result = true; 698 NamedNodeMap attributes = element.getAttributes(); 699 for (int i = 0; i < attributes.getLength(); i++) { 700 String attribute = attributes.item(i).getNodeName(); 701 String value = attributes.item(i).getNodeValue(); 702 switch (attribute) { 703 case "mcc": 704 result = result && value.equals(id.getMcc()); 705 break; 706 case "mnc": 707 result = result && value.equals(id.getMnc()); 708 break; 709 case "gid1": 710 result = result && Ascii.equalsIgnoreCase(value, id.getGid1()); 711 break; 712 case "spn": 713 result = result && matchOnSP(value, id); 714 break; 715 case "imsi": 716 result = result && matchOnImsi(value, id); 717 break; 718 case "cid": 719 result = 720 result 721 && ((Integer.parseInt(value) == id.getCarrierId()) 722 || (Integer.parseInt(value) == id.getSpecificCarrierId())); 723 break; 724 case "name": 725 // name is used together with cid for readability. ignore for filter. 726 break; 727 default: 728 System.err.println("Unsupported attribute " + attribute + "=" + value); 729 result = false; 730 } 731 } 732 return result; 733 } 734 735 /** 736 * Returns {@code true} if an "spn" attribute in <carrier_config ...> element matches the carrier 737 * identifier. 738 * 739 * <p>Copied from AOSP DefaultCarrierConfigService. 740 */ matchOnSP(String xmlSP, CarrierIdentifier id)741 private static boolean matchOnSP(String xmlSP, CarrierIdentifier id) { 742 boolean matchFound = false; 743 744 String currentSP = id.getSpn(); 745 // <carrier_config ... spn="null"> means expecting SIM SPN empty in AOSP convention. 746 if (Ascii.equalsIgnoreCase("null", xmlSP)) { 747 if (currentSP.isEmpty()) { 748 matchFound = true; 749 } 750 } else if (currentSP != null) { 751 Pattern spPattern = Pattern.compile(xmlSP, Pattern.CASE_INSENSITIVE); 752 Matcher matcher = spPattern.matcher(currentSP); 753 matchFound = matcher.matches(); 754 } 755 return matchFound; 756 } 757 758 /** 759 * Returns {@code true} if an "imsi" attribute in <carrier_config ...> element matches the carrier 760 * identifier. 761 * 762 * <p>Copied from AOSP DefaultCarrierConfigService. 763 */ matchOnImsi(String xmlImsi, CarrierIdentifier id)764 private static boolean matchOnImsi(String xmlImsi, CarrierIdentifier id) { 765 boolean matchFound = false; 766 767 String currentImsi = id.getImsi(); 768 // If we were able to retrieve current IMSI, see if it matches. 769 if (currentImsi != null) { 770 Pattern imsiPattern = Pattern.compile(xmlImsi, Pattern.CASE_INSENSITIVE); 771 Matcher matcher = imsiPattern.matcher(currentImsi); 772 matchFound = matcher.matches(); 773 } 774 return matchFound; 775 } 776 777 /** 778 * Parses a {@link CarrierId} out of a <carrier_config ...> tag. 779 * 780 * <p>This is purely used for discover potential carriers expressed by this tag, the return value 781 * may not reflect all attributes of the tag. 782 */ parseCarrierId(Element element)783 private static CarrierId.Builder parseCarrierId(Element element) { 784 CarrierId.Builder builder = CarrierId.newBuilder(); 785 String mccMnc = element.getAttribute("mcc") + element.getAttribute("mnc"); 786 builder.setMccMnc(mccMnc); 787 if (element.hasAttribute("imsi")) { 788 builder.setImsi(element.getAttribute("imsi")); 789 } else if (element.hasAttribute("gid1")) { 790 builder.setGid1(element.getAttribute("gid1")); 791 } else if (element.hasAttribute("gid2")) { 792 throw new UnsupportedOperationException( 793 "Not support attribute `gid2`: " + element.getAttribute("gid2")); 794 } else if (element.hasAttribute("spn")) { 795 builder.setSpn(element.getAttribute("spn")); 796 } 797 return builder; 798 } 799 800 // Same as {@link java.nio.file.Paths#get} but returns a String getPathAsString(String first, String... more)801 private static String getPathAsString(String first, String... more) { 802 return java.nio.file.Paths.get(first, more).toString(); 803 } 804 805 /** Mirror of Android CarrierIdentifier class. Default value of a carrier id is -1. */ 806 @AutoValue 807 abstract static class CarrierIdentifier { getMcc()808 abstract String getMcc(); 809 getMnc()810 abstract String getMnc(); 811 getImsi()812 abstract String getImsi(); 813 getGid1()814 abstract String getGid1(); 815 getSpn()816 abstract String getSpn(); 817 getCarrierId()818 abstract int getCarrierId(); 819 getSpecificCarrierId()820 abstract int getSpecificCarrierId(); 821 getMccmncCarrierId()822 abstract int getMccmncCarrierId(); 823 create( CarrierId carrier, int carrierId, int specificCarrierId, int mccmncCarrierId)824 static CarrierIdentifier create( 825 CarrierId carrier, int carrierId, int specificCarrierId, int mccmncCarrierId) { 826 String mcc = carrier.getMccMnc().substring(0, 3); 827 String mnc = carrier.getMccMnc().length() > 3 ? carrier.getMccMnc().substring(3) : ""; 828 return new AutoValue_CarrierConfigConverterV2_CarrierIdentifier( 829 mcc, 830 mnc, 831 carrier.getImsi(), 832 carrier.getGid1(), 833 carrier.getSpn(), 834 carrierId, 835 specificCarrierId, 836 mccmncCarrierId); 837 } 838 } 839 getCid( CarrierId carrierId, Multimap<CarrierId, Integer> reverseAospCarrierList)840 private static CarrierIdentifier getCid( 841 CarrierId carrierId, Multimap<CarrierId, Integer> reverseAospCarrierList) { 842 // Mimic TelephonyManager#getCarrierIdFromMccMnc, which is implemented by 843 // CarrierResolver#getCarrierIdFromMccMnc. 844 CarrierId mccMnc = CarrierId.newBuilder().setMccMnc(carrierId.getMccMnc()).build(); 845 int mccMncCarrierId = reverseAospCarrierList.get(mccMnc).stream().findFirst().orElse(-1); 846 847 List<Integer> cids = ImmutableList.copyOf(reverseAospCarrierList.get(carrierId)); 848 // No match: use -1 849 if (cids.isEmpty()) { 850 return CarrierIdentifier.create(carrierId, -1, -1, mccMncCarrierId); 851 } 852 // One match: use as both carrierId and specificCarrierId 853 if (cids.size() == 1) { 854 return CarrierIdentifier.create(carrierId, cids.get(0), cids.get(0), mccMncCarrierId); 855 } 856 // Two matches: specificCarrierId is always bigger than carrierId 857 if (cids.size() == 2) { 858 return CarrierIdentifier.create( 859 carrierId, 860 Math.min(cids.get(0), cids.get(1)), 861 Math.max(cids.get(0), cids.get(1)), 862 mccMncCarrierId); 863 } 864 // Cannot be more than 2 matches. 865 throw new IllegalStateException("More than two cid's found for " + carrierId + ": " + cids); 866 } 867 CarrierConfigConverterV2()868 private CarrierConfigConverterV2() {} 869 } 870