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