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