xref: /aosp_15_r20/tools/carrier_settings/java/CarrierConfigConverterV2.java (revision ff35212d322a3e892605b94fa777c67085d45efd)
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