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.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.File;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.util.HashMap;
21 
22 import com.android.internal.util.FastXmlSerializer;
23 
24 /**
25  * Provides network overrides for carrier configuration.
26  *
27  * The configuration available through CarrierConfigManager is a combination of default values,
28  * default network overrides, and carrier overrides. The default network overrides are provided by
29  * this service. For a given network, we look for a matching XML file in our assets folder, and
30  * return the PersistableBundle from that file. Assets are preferred over Resources because resource
31  * overlays only support using MCC+MNC and that doesn't work with MVNOs. The only resource file used
32  * is vendor.xml, to provide vendor-specific overrides.
33  */
34 public class DefaultCarrierConfigService extends CarrierService {
35 
36     private static final String TAG = "DefaultCarrierConfigService";
37 
38     private XmlPullParserFactory mFactory;
39 
DefaultCarrierConfigService()40     public DefaultCarrierConfigService() {
41         Log.d(TAG, "Service created");
42         mFactory = null;
43     }
44 
45     /**
46      * Returns per-network overrides for carrier configuration.
47      *
48      * This returns a carrier config bundle appropriate for the given network by reading data from
49      * files in our assets folder. First we look for a file named after the MCC+MNC of {@code id}
50      * and then we read res/xml/vendor.xml. Both files may contain multiple bundles with filters on
51      * them. All the matching bundles are flattened to return one carrier config bundle.
52      */
53     @Override
onLoadConfig(CarrierIdentifier id)54     public PersistableBundle onLoadConfig(CarrierIdentifier id) {
55         Log.d(TAG, "Config being fetched");
56 
57         if (id == null) {
58             return null;
59         }
60 
61 
62         PersistableBundle config = null;
63         try {
64             synchronized (this) {
65                 if (mFactory == null) {
66                     mFactory = XmlPullParserFactory.newInstance();
67                 }
68             }
69 
70             XmlPullParser parser = mFactory.newPullParser();
71             String fileName = "carrier_config_" + id.getMcc() + id.getMnc() + ".xml";
72             parser.setInput(getApplicationContext().getAssets().open(fileName), "utf-8");
73             config = readConfigFromXml(parser, id);
74         }
75         catch (IOException | XmlPullParserException e) {
76             Log.d(TAG, e.toString());
77             // We can return an empty config for unknown networks.
78             config = new PersistableBundle();
79         }
80 
81         // Treat vendor.xml as if it were appended to the carrier config file we read.
82         XmlPullParser vendorInput = getApplicationContext().getResources().getXml(R.xml.vendor);
83         try {
84             PersistableBundle vendorConfig = readConfigFromXml(vendorInput, id);
85             config.putAll(vendorConfig);
86         }
87         catch (IOException | XmlPullParserException e) {
88             Log.e(TAG, e.toString());
89         }
90 
91         return config;
92     }
93 
94     /**
95      * Parses an XML document and returns a PersistableBundle.
96      *
97      * <p>This function iterates over each {@code <carrier_config>} node in the XML document and
98      * parses it into a bundle if its filters match {@code id}. The format of XML bundles is defined
99      * by {@link PersistableBundle#restoreFromXml}. All the matching bundles will be flattened and
100      * returned as a single bundle.</p>
101      *
102      * <p>Here is an example document. The second bundle will be applied to the first only if the
103      * GID1 is ABCD.
104      * <pre>{@code
105      * <carrier_config_list>
106      *     <carrier_config>
107      *         <boolean name="voicemail_notification_persistent_bool" value="true" />
108      *     </carrier_config>
109      *     <carrier_config gid1="ABCD">
110      *         <boolean name="voicemail_notification_persistent_bool" value="false" />
111      *     </carrier_config>
112      * </carrier_config_list>
113      * }</pre></p>
114      *
115      * @param parser an XmlPullParser pointing at the beginning of the document.
116      * @param id the details of the SIM operator used to filter parts of the document
117      * @return a possibly empty PersistableBundle containing the config values.
118      */
readConfigFromXml(XmlPullParser parser, CarrierIdentifier id)119     static PersistableBundle readConfigFromXml(XmlPullParser parser, CarrierIdentifier id)
120             throws IOException, XmlPullParserException {
121         PersistableBundle config = new PersistableBundle();
122 
123         if (parser == null) {
124           return config;
125         }
126 
127         // Iterate over each <carrier_config> node in the document and add it to the returned
128         // bundle if its filters match.
129         int event;
130         while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
131             if (event == XmlPullParser.START_TAG && "carrier_config".equals(parser.getName())) {
132                 // Skip this fragment if it has filters that don't match.
133                 if (!checkFilters(parser, id)) {
134                     continue;
135                 }
136                 PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser);
137                 config.putAll(configFragment);
138             }
139         }
140 
141         return config;
142     }
143 
144     /**
145      * Checks to see if an XML node matches carrier filters.
146      *
147      * <p>This iterates over the attributes of the current tag pointed to by {@code parser} and
148      * checks each one against {@code id} or {@link Build.DEVICE}. Attributes that are not specified
149      * in the node will not be checked, so a node with no attributes will always return true. The
150      * supported filter attributes are,
151      * <ul>
152      *   <li>mcc: {@link CarrierIdentifier#getMcc}</li>
153      *   <li>mnc: {@link CarrierIdentifier#getMnc}</li>
154      *   <li>gid1: {@link CarrierIdentifier#getGid1}</li>
155      *   <li>gid2: {@link CarrierIdentifier#getGid2}</li>
156      *   <li>spn: {@link CarrierIdentifier#getSpn}</li>
157      *   <li>device: {@link Build.DEVICE}</li>
158      * </ul>
159      * </p>
160      *
161      * @param parser an XmlPullParser pointing at a START_TAG with the attributes to check.
162      * @param id the carrier details to check against.
163      * @return false if any XML attribute does not match the corresponding value.
164      */
checkFilters(XmlPullParser parser, CarrierIdentifier id)165     static boolean checkFilters(XmlPullParser parser, CarrierIdentifier id) {
166         boolean result = true;
167         for (int i = 0; i < parser.getAttributeCount(); ++i) {
168             String attribute = parser.getAttributeName(i);
169             String value = parser.getAttributeValue(i);
170             switch (attribute) {
171                 case "mcc":
172                     result = result && value.equals(id.getMcc());
173                     break;
174                 case "mnc":
175                     result = result && value.equals(id.getMnc());
176                     break;
177                 case "gid1":
178                     result = result && value.equals(id.getGid1());
179                     break;
180                 case "gid2":
181                     result = result && value.equals(id.getGid2());
182                     break;
183                 case "spn":
184                     result = result && value.equals(id.getSpn());
185                     break;
186                 case "device":
187                     result = result && value.equals(Build.DEVICE);
188                     break;
189                 default:
190                     Log.e(TAG, "Unknown attribute " + attribute + "=" + value);
191                     result = false;
192                     break;
193             }
194         }
195         return result;
196     }
197 }
198