blob: b82cce6515a33dd616ce4a6d3a7027cd5904a3d0 [file] [log] [blame]
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -07001package com.android.carrierconfig;
2
Jonathan Basseri449236a2015-06-04 15:19:58 -07003import android.content.Context;
4import android.os.Build;
Jonathan Basseri202b0f42015-05-12 13:53:12 -07005import android.os.PersistableBundle;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -07006import android.service.carrier.CarrierIdentifier;
Jonathan Basseri449236a2015-06-04 15:19:58 -07007import android.service.carrier.CarrierService;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -07008import android.telephony.CarrierConfigManager;
Nancy Chen2070ccf2015-05-22 17:31:04 -07009import android.telephony.TelephonyManager;
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +010010import android.text.TextUtils;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070011import android.util.Log;
12
Jonathan Basseri449236a2015-06-04 15:19:58 -070013import org.xmlpull.v1.XmlPullParser;
14import org.xmlpull.v1.XmlPullParserException;
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070015import org.xmlpull.v1.XmlPullParserFactory;
Jonathan Basseri449236a2015-06-04 15:19:58 -070016
17import java.io.File;
18import java.io.FileOutputStream;
19import java.io.IOException;
20import java.io.InputStream;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070021import java.util.HashMap;
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +010022import java.util.regex.Matcher;
23import java.util.regex.Pattern;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070024
Jonathan Basseri449236a2015-06-04 15:19:58 -070025import 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 */
Zach Johnson9d385622015-05-26 10:16:17 -070037public class DefaultCarrierConfigService extends CarrierService {
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070038
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +010039 private static final String SPN_EMPTY_MATCH = "null";
40
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070041 private static final String TAG = "DefaultCarrierConfigService";
42
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070043 private XmlPullParserFactory mFactory;
44
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070045 public DefaultCarrierConfigService() {
46 Log.d(TAG, "Service created");
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070047 mFactory = null;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070048 }
49
Jonathan Basseri449236a2015-06-04 15:19:58 -070050 /**
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 */
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070058 @Override
Jonathan Basseri202b0f42015-05-12 13:53:12 -070059 public PersistableBundle onLoadConfig(CarrierIdentifier id) {
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -070060 Log.d(TAG, "Config being fetched");
Jonathan Basseri449236a2015-06-04 15:19:58 -070061
62 if (id == null) {
63 return null;
64 }
65
Jonathan Basserieb8ef012015-06-22 20:15:19 -070066
67 PersistableBundle config = null;
Jonathan Basseri449236a2015-06-04 15:19:58 -070068 try {
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070069 synchronized (this) {
70 if (mFactory == null) {
71 mFactory = XmlPullParserFactory.newInstance();
72 }
73 }
Jonathan Basserieb8ef012015-06-22 20:15:19 -070074
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070075 XmlPullParser parser = mFactory.newPullParser();
Jonathan Basserieb8ef012015-06-22 20:15:19 -070076 String fileName = "carrier_config_" + id.getMcc() + id.getMnc() + ".xml";
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070077 parser.setInput(getApplicationContext().getAssets().open(fileName), "utf-8");
Jonathan Basserieb8ef012015-06-22 20:15:19 -070078 config = readConfigFromXml(parser, id);
Jonathan Basseri449236a2015-06-04 15:19:58 -070079 }
Jonathan Basseri6c8cffb2015-06-23 14:42:04 -070080 catch (IOException | XmlPullParserException e) {
Jonathan Basserieb8ef012015-06-22 20:15:19 -070081 Log.d(TAG, e.toString());
82 // We can return an empty config for unknown networks.
83 config = new PersistableBundle();
Jonathan Basseri449236a2015-06-04 15:19:58 -070084 }
Jonathan Basserieb8ef012015-06-22 20:15:19 -070085
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;
Jonathan Basseri449236a2015-06-04 15:19:58 -070097 }
98
99 /**
100 * Parses an XML document and returns a PersistableBundle.
101 *
Jonathan Basserieb8ef012015-06-22 20:15:19 -0700102 * <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
Jonathan Basseri449236a2015-06-04 15:19:58 -0700105 * 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 */
Jonathan Basserieb8ef012015-06-22 20:15:19 -0700124 static PersistableBundle readConfigFromXml(XmlPullParser parser, CarrierIdentifier id)
125 throws IOException, XmlPullParserException {
Jonathan Basseri449236a2015-06-04 15:19:58 -0700126 PersistableBundle config = new PersistableBundle();
127
128 if (parser == null) {
129 return config;
130 }
131
Jonathan Basserieb8ef012015-06-22 20:15:19 -0700132 // 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;
Jonathan Basseri449236a2015-06-04 15:19:58 -0700140 }
Jonathan Basserieb8ef012015-06-22 20:15:19 -0700141 PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser);
142 config.putAll(configFragment);
Jonathan Basseri449236a2015-06-04 15:19:58 -0700143 }
144 }
Jonathan Basseri449236a2015-06-04 15:19:58 -0700145
146 return config;
147 }
148
149 /**
150 * Checks to see if an XML node matches carrier filters.
151 *
Jonathan Basserieb8ef012015-06-22 20:15:19 -0700152 * <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,
Jonathan Basseri449236a2015-06-04 15:19:58 -0700156 * <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>
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +0100162 * <li>imsi: {@link CarrierIdentifier#getImsi}</li>
Jonathan Basseri449236a2015-06-04 15:19:58 -0700163 * <li>device: {@link Build.DEVICE}</li>
164 * </ul>
165 * </p>
166 *
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +0100167 * <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 *
Jonathan Basseri449236a2015-06-04 15:19:58 -0700173 * @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 */
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":
Meng Wang06657602016-11-17 10:38:23 -0800190 result = result && value.equalsIgnoreCase(id.getGid1());
Jonathan Basseri449236a2015-06-04 15:19:58 -0700191 break;
192 case "gid2":
Meng Wang06657602016-11-17 10:38:23 -0800193 result = result && value.equalsIgnoreCase(id.getGid2());
Jonathan Basseri449236a2015-06-04 15:19:58 -0700194 break;
195 case "spn":
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +0100196 result = result && matchOnSP(value, id);
197 break;
198 case "imsi":
199 result = result && matchOnImsi(value, id);
Jonathan Basseri449236a2015-06-04 15:19:58 -0700200 break;
201 case "device":
Meng Wang06657602016-11-17 10:38:23 -0800202 result = result && value.equalsIgnoreCase(Build.DEVICE);
Jonathan Basseri449236a2015-06-04 15:19:58 -0700203 break;
204 default:
205 Log.e(TAG, "Unknown attribute " + attribute + "=" + value);
206 result = false;
207 break;
208 }
209 }
210 return result;
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -0700211 }
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +0100212
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 */
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) {
Meng Wang378f2092016-12-07 15:58:08 -0800228 Pattern imsiPattern = Pattern.compile(xmlImsi, Pattern.CASE_INSENSITIVE);
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +0100229 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 */
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) {
Meng Wang378f2092016-12-07 15:58:08 -0800252 Pattern spPattern = Pattern.compile(xmlSP, Pattern.CASE_INSENSITIVE);
Peter Ljungdahl2f9d2a52015-11-16 13:03:23 +0100253 Matcher matcher = spPattern.matcher(currentSP);
254 matchFound = matcher.matches();
255 }
256 return matchFound;
257 }
Jonathan Basseri3a4ccd22015-03-18 11:22:19 -0700258}