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