• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.carrierconfig;
2 
3 import android.content.Context;
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.CarrierConfigManager;
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.File;
18 import java.io.FileOutputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.util.HashMap;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 
25 import com.android.internal.util.FastXmlSerializer;
26 
27 /**
28  * Provides network overrides for carrier configuration.
29  *
30  * The configuration available through CarrierConfigManager is a combination of default values,
31  * default network overrides, and carrier overrides. The default network overrides are provided by
32  * this service. For a given network, we look for a matching XML file in our assets folder, and
33  * return the PersistableBundle from that file. Assets are preferred over Resources because resource
34  * overlays only support using MCC+MNC and that doesn't work with MVNOs. The only resource file used
35  * is vendor.xml, to provide vendor-specific overrides.
36  */
37 public class DefaultCarrierConfigService extends CarrierService {
38 
39     private static final String SPN_EMPTY_MATCH = "null";
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 network by reading data from
54      * files in our assets folder. First we look for a file named after the MCC+MNC of {@code id}
55      * and then we read res/xml/vendor.xml. Both files may contain multiple bundles with filters on
56      * them. All the matching bundles are flattened to return one carrier config bundle.
57      */
58     @Override
onLoadConfig(CarrierIdentifier id)59     public PersistableBundle onLoadConfig(CarrierIdentifier id) {
60         Log.d(TAG, "Config being fetched");
61 
62         if (id == null) {
63             return null;
64         }
65 
66 
67         PersistableBundle config = null;
68         try {
69             synchronized (this) {
70                 if (mFactory == null) {
71                     mFactory = XmlPullParserFactory.newInstance();
72                 }
73             }
74 
75             XmlPullParser parser = mFactory.newPullParser();
76             String fileName = "carrier_config_" + id.getMcc() + id.getMnc() + ".xml";
77             parser.setInput(getApplicationContext().getAssets().open(fileName), "utf-8");
78             config = readConfigFromXml(parser, id);
79         }
80         catch (IOException | XmlPullParserException e) {
81             Log.d(TAG, e.toString());
82             // We can return an empty config for unknown networks.
83             config = new PersistableBundle();
84         }
85 
86         // Treat vendor.xml as if it were appended to the carrier config file we read.
87         XmlPullParser vendorInput = getApplicationContext().getResources().getXml(R.xml.vendor);
88         try {
89             PersistableBundle vendorConfig = readConfigFromXml(vendorInput, id);
90             config.putAll(vendorConfig);
91         }
92         catch (IOException | XmlPullParserException e) {
93             Log.e(TAG, e.toString());
94         }
95 
96         return config;
97     }
98 
99     /**
100      * Parses an XML document and returns a PersistableBundle.
101      *
102      * <p>This function iterates over each {@code <carrier_config>} node in the XML document and
103      * parses it into a bundle if its filters match {@code id}. The format of XML bundles is defined
104      * by {@link PersistableBundle#restoreFromXml}. All the matching bundles will be flattened and
105      * returned as a single bundle.</p>
106      *
107      * <p>Here is an example document. The second bundle will be applied to the first only if the
108      * GID1 is ABCD.
109      * <pre>{@code
110      * <carrier_config_list>
111      *     <carrier_config>
112      *         <boolean name="voicemail_notification_persistent_bool" value="true" />
113      *     </carrier_config>
114      *     <carrier_config gid1="ABCD">
115      *         <boolean name="voicemail_notification_persistent_bool" value="false" />
116      *     </carrier_config>
117      * </carrier_config_list>
118      * }</pre></p>
119      *
120      * @param parser an XmlPullParser pointing at the beginning of the document.
121      * @param id the details of the SIM operator used to filter parts of the document
122      * @return a possibly empty PersistableBundle containing the config values.
123      */
readConfigFromXml(XmlPullParser parser, CarrierIdentifier id)124     static PersistableBundle readConfigFromXml(XmlPullParser parser, CarrierIdentifier id)
125             throws IOException, XmlPullParserException {
126         PersistableBundle config = new PersistableBundle();
127 
128         if (parser == null) {
129           return config;
130         }
131 
132         // Iterate over each <carrier_config> node in the document and add it to the returned
133         // bundle if its filters match.
134         int event;
135         while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
136             if (event == XmlPullParser.START_TAG && "carrier_config".equals(parser.getName())) {
137                 // Skip this fragment if it has filters that don't match.
138                 if (!checkFilters(parser, id)) {
139                     continue;
140                 }
141                 PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser);
142                 config.putAll(configFragment);
143             }
144         }
145 
146         return config;
147     }
148 
149     /**
150      * Checks to see if an XML node matches carrier filters.
151      *
152      * <p>This iterates over the attributes of the current tag pointed to by {@code parser} and
153      * checks each one against {@code id} or {@link Build.DEVICE}. Attributes that are not specified
154      * in the node will not be checked, so a node with no attributes will always return true. The
155      * supported filter attributes are,
156      * <ul>
157      *   <li>mcc: {@link CarrierIdentifier#getMcc}</li>
158      *   <li>mnc: {@link CarrierIdentifier#getMnc}</li>
159      *   <li>gid1: {@link CarrierIdentifier#getGid1}</li>
160      *   <li>gid2: {@link CarrierIdentifier#getGid2}</li>
161      *   <li>spn: {@link CarrierIdentifier#getSpn}</li>
162      *   <li>imsi: {@link CarrierIdentifier#getImsi}</li>
163      *   <li>device: {@link Build.DEVICE}</li>
164      * </ul>
165      * </p>
166      *
167      * <p>
168      * The attributes imsi and spn can be expressed as regexp to filter on patterns.
169      * The spn attribute can be set to the string "null" to allow matching against a SIM
170      * with no spn set.
171      * </p>
172      *
173      * @param parser an XmlPullParser pointing at a START_TAG with the attributes to check.
174      * @param id the carrier details to check against.
175      * @return false if any XML attribute does not match the corresponding value.
176      */
checkFilters(XmlPullParser parser, CarrierIdentifier id)177     static boolean checkFilters(XmlPullParser parser, CarrierIdentifier id) {
178         boolean result = true;
179         for (int i = 0; i < parser.getAttributeCount(); ++i) {
180             String attribute = parser.getAttributeName(i);
181             String value = parser.getAttributeValue(i);
182             switch (attribute) {
183                 case "mcc":
184                     result = result && value.equals(id.getMcc());
185                     break;
186                 case "mnc":
187                     result = result && value.equals(id.getMnc());
188                     break;
189                 case "gid1":
190                     result = result && value.equalsIgnoreCase(id.getGid1());
191                     break;
192                 case "gid2":
193                     result = result && value.equalsIgnoreCase(id.getGid2());
194                     break;
195                 case "spn":
196                     result = result && matchOnSP(value, id);
197                     break;
198                 case "imsi":
199                     result = result && matchOnImsi(value, id);
200                     break;
201                 case "device":
202                     result = result && value.equalsIgnoreCase(Build.DEVICE);
203                     break;
204                 default:
205                     Log.e(TAG, "Unknown attribute " + attribute + "=" + value);
206                     result = false;
207                     break;
208             }
209         }
210         return result;
211     }
212 
213     /**
214      * Check to see if the IMSI expression from the XML matches the IMSI of the
215      * Carrier.
216      *
217      * @param xmlImsi IMSI expression fetched from the resource XML
218      * @param id Id of the evaluated CarrierIdentifier
219      * @return true if the XML IMSI matches the IMSI of CarrierIdentifier, false
220      *         otherwise.
221      */
matchOnImsi(String xmlImsi, CarrierIdentifier id)222     static boolean matchOnImsi(String xmlImsi, CarrierIdentifier id) {
223         boolean matchFound = false;
224 
225         String currentImsi = id.getImsi();
226         // If we were able to retrieve current IMSI, see if it matches.
227         if (currentImsi != null) {
228             Pattern imsiPattern = Pattern.compile(xmlImsi, Pattern.CASE_INSENSITIVE);
229             Matcher matcher = imsiPattern.matcher(currentImsi);
230             matchFound = matcher.matches();
231         }
232         return matchFound;
233     }
234 
235     /**
236      * Check to see if the service provider name expression from the XML matches the
237      * CarrierIdentifier.
238      *
239      * @param xmlSP SP expression fetched from the resource XML
240      * @param id Id of the evaluated CarrierIdentifier
241      * @return true if the XML SP matches the phone's SP, false otherwise.
242      */
matchOnSP(String xmlSP, CarrierIdentifier id)243     static boolean matchOnSP(String xmlSP, CarrierIdentifier id) {
244         boolean matchFound = false;
245 
246         String currentSP = id.getSpn();
247         if (SPN_EMPTY_MATCH.equalsIgnoreCase(xmlSP)) {
248             if (TextUtils.isEmpty(currentSP)) {
249                 matchFound = true;
250             }
251         } else if (currentSP != null) {
252             Pattern spPattern = Pattern.compile(xmlSP, Pattern.CASE_INSENSITIVE);
253             Matcher matcher = spPattern.matcher(currentSP);
254             matchFound = matcher.matches();
255         }
256         return matchFound;
257     }
258 }
259