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