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