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