Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 1 | package com.android.carrierconfig; |
| 2 | |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 3 | import android.content.Context; |
| 4 | import android.os.Build; |
Jonathan Basseri | 202b0f4 | 2015-05-12 13:53:12 -0700 | [diff] [blame] | 5 | import android.os.PersistableBundle; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 6 | import android.service.carrier.CarrierIdentifier; |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 7 | import android.service.carrier.CarrierService; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 8 | import android.telephony.CarrierConfigManager; |
Nancy Chen | 2070ccf | 2015-05-22 17:31:04 -0700 | [diff] [blame] | 9 | import android.telephony.TelephonyManager; |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 10 | import android.text.TextUtils; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 11 | import android.util.Log; |
| 12 | |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 13 | import org.xmlpull.v1.XmlPullParser; |
| 14 | import org.xmlpull.v1.XmlPullParserException; |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 15 | import org.xmlpull.v1.XmlPullParserFactory; |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 16 | |
| 17 | import java.io.File; |
| 18 | import java.io.FileOutputStream; |
| 19 | import java.io.IOException; |
| 20 | import java.io.InputStream; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 21 | import java.util.HashMap; |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 22 | import java.util.regex.Matcher; |
| 23 | import java.util.regex.Pattern; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 24 | |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 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 | */ |
Zach Johnson | 9d38562 | 2015-05-26 10:16:17 -0700 | [diff] [blame] | 37 | public class DefaultCarrierConfigService extends CarrierService { |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 38 | |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 39 | private static final String SPN_EMPTY_MATCH = "null"; |
| 40 | |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 41 | private static final String TAG = "DefaultCarrierConfigService"; |
| 42 | |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 43 | private XmlPullParserFactory mFactory; |
| 44 | |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 45 | public DefaultCarrierConfigService() { |
| 46 | Log.d(TAG, "Service created"); |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 47 | mFactory = null; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 48 | } |
| 49 | |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 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 | */ |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 58 | @Override |
Jonathan Basseri | 202b0f4 | 2015-05-12 13:53:12 -0700 | [diff] [blame] | 59 | public PersistableBundle onLoadConfig(CarrierIdentifier id) { |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 60 | Log.d(TAG, "Config being fetched"); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 61 | |
| 62 | if (id == null) { |
| 63 | return null; |
| 64 | } |
| 65 | |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 66 | |
| 67 | PersistableBundle config = null; |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 68 | try { |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 69 | synchronized (this) { |
| 70 | if (mFactory == null) { |
| 71 | mFactory = XmlPullParserFactory.newInstance(); |
| 72 | } |
| 73 | } |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 74 | |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 75 | XmlPullParser parser = mFactory.newPullParser(); |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 76 | String fileName = "carrier_config_" + id.getMcc() + id.getMnc() + ".xml"; |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 77 | parser.setInput(getApplicationContext().getAssets().open(fileName), "utf-8"); |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 78 | config = readConfigFromXml(parser, id); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 79 | } |
Jonathan Basseri | 6c8cffb | 2015-06-23 14:42:04 -0700 | [diff] [blame] | 80 | catch (IOException | XmlPullParserException e) { |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 81 | Log.d(TAG, e.toString()); |
| 82 | // We can return an empty config for unknown networks. |
| 83 | config = new PersistableBundle(); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 84 | } |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 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; |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | /** |
| 100 | * Parses an XML document and returns a PersistableBundle. |
| 101 | * |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 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 |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 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 | */ |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 124 | static PersistableBundle readConfigFromXml(XmlPullParser parser, CarrierIdentifier id) |
| 125 | throws IOException, XmlPullParserException { |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 126 | PersistableBundle config = new PersistableBundle(); |
| 127 | |
| 128 | if (parser == null) { |
| 129 | return config; |
| 130 | } |
| 131 | |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 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; |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 140 | } |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 141 | PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser); |
| 142 | config.putAll(configFragment); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 143 | } |
| 144 | } |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 145 | |
| 146 | return config; |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Checks to see if an XML node matches carrier filters. |
| 151 | * |
Jonathan Basseri | eb8ef01 | 2015-06-22 20:15:19 -0700 | [diff] [blame] | 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, |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 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> |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 162 | * <li>imsi: {@link CarrierIdentifier#getImsi}</li> |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 163 | * <li>device: {@link Build.DEVICE}</li> |
| 164 | * </ul> |
| 165 | * </p> |
| 166 | * |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 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 | * |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 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 | */ |
| 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 Wang | 0665760 | 2016-11-17 10:38:23 -0800 | [diff] [blame] | 190 | result = result && value.equalsIgnoreCase(id.getGid1()); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 191 | break; |
| 192 | case "gid2": |
Meng Wang | 0665760 | 2016-11-17 10:38:23 -0800 | [diff] [blame] | 193 | result = result && value.equalsIgnoreCase(id.getGid2()); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 194 | break; |
| 195 | case "spn": |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 196 | result = result && matchOnSP(value, id); |
| 197 | break; |
| 198 | case "imsi": |
| 199 | result = result && matchOnImsi(value, id); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 200 | break; |
| 201 | case "device": |
Meng Wang | 0665760 | 2016-11-17 10:38:23 -0800 | [diff] [blame] | 202 | result = result && value.equalsIgnoreCase(Build.DEVICE); |
Jonathan Basseri | 449236a | 2015-06-04 15:19:58 -0700 | [diff] [blame] | 203 | break; |
| 204 | default: |
| 205 | Log.e(TAG, "Unknown attribute " + attribute + "=" + value); |
| 206 | result = false; |
| 207 | break; |
| 208 | } |
| 209 | } |
| 210 | return result; |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 211 | } |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 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 | */ |
| 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 Wang | 378f209 | 2016-12-07 15:58:08 -0800 | [diff] [blame] | 228 | Pattern imsiPattern = Pattern.compile(xmlImsi, Pattern.CASE_INSENSITIVE); |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 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 | */ |
| 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 Wang | 378f209 | 2016-12-07 15:58:08 -0800 | [diff] [blame] | 252 | Pattern spPattern = Pattern.compile(xmlSP, Pattern.CASE_INSENSITIVE); |
Peter Ljungdahl | 2f9d2a5 | 2015-11-16 13:03:23 +0100 | [diff] [blame] | 253 | Matcher matcher = spPattern.matcher(currentSP); |
| 254 | matchFound = matcher.matches(); |
| 255 | } |
| 256 | return matchFound; |
| 257 | } |
Jonathan Basseri | 3a4ccd2 | 2015-03-18 11:22:19 -0700 | [diff] [blame] | 258 | } |