1 package com.android.carrierconfig; 2 3 import android.Manifest; 4 import android.annotation.NonNull; 5 import android.content.Context; 6 import android.content.res.AssetManager; 7 import android.content.res.Resources; 8 import android.database.Cursor; 9 import android.os.PersistableBundle; 10 import android.provider.Telephony; 11 import android.service.carrier.CarrierIdentifier; 12 import android.telephony.CarrierConfigManager; 13 import android.telephony.TelephonyManager; 14 import android.test.InstrumentationTestCase; 15 import android.util.Log; 16 17 import java.io.IOException; 18 import java.io.InputStream; 19 import java.lang.reflect.Field; 20 import java.lang.reflect.Modifier; 21 import java.util.ArrayList; 22 import java.util.HashSet; 23 import java.util.List; 24 import java.util.Set; 25 26 import junit.framework.AssertionFailedError; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 import org.xmlpull.v1.XmlPullParserFactory; 31 32 public class CarrierConfigTest extends InstrumentationTestCase { 33 private static final String TAG = "CarrierConfigTest"; 34 35 /** 36 * Iterate over all XML files in assets/ and ensure they parse without error. 37 */ testAllFilesParse()38 public void testAllFilesParse() { 39 forEachConfigXml(new ParserChecker() { 40 public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException, 41 IOException { 42 PersistableBundle b = DefaultCarrierConfigService.readConfigFromXml(parser, 43 new CarrierIdentifier("001", "001", "Test", "001001123456789", "", "")); 44 assertNotNull("got null bundle", b); 45 } 46 }); 47 } 48 49 /** 50 * Check that the config bundles in XML files have valid filter attributes. 51 * This checks the attribute names only. 52 */ testFilterValidAttributes()53 public void testFilterValidAttributes() { 54 forEachConfigXml(new ParserChecker() { 55 public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException, 56 IOException { 57 int event; 58 while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) { 59 if (event == XmlPullParser.START_TAG 60 && "carrier_config".equals(parser.getName())) { 61 for (int i = 0; i < parser.getAttributeCount(); ++i) { 62 String attribute = parser.getAttributeName(i); 63 switch (attribute) { 64 case "mcc": 65 case "mnc": 66 case "gid1": 67 case "gid2": 68 case "spn": 69 case "imsi": 70 case "device": 71 case "cid": 72 case "name": 73 break; 74 default: 75 fail("Unknown attribute '" + attribute 76 + "' at " + parser.getPositionDescription()); 77 break; 78 } 79 } 80 } 81 } 82 } 83 }); 84 } 85 86 /** 87 * Check that XML files named after mccmnc are those without matching carrier id. 88 * If there is a matching carrier id, all configurations should move to carrierid.xml which 89 * has a higher matching priority than mccmnc.xml 90 */ testCarrierConfigFileNaming()91 public void testCarrierConfigFileNaming() { 92 forEachConfigXml(new ParserChecker() { 93 public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException, 94 IOException { 95 if (mccmnc == null) { 96 // only check file named after mccmnc 97 return; 98 } 99 int event; 100 while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) { 101 if (event == XmlPullParser.START_TAG 102 && "carrier_config".equals(parser.getName())) { 103 String mcc = null; 104 String mnc = null; 105 String spn = null; 106 String gid1 = null; 107 String gid2 = null; 108 String imsi = null; 109 for (int i = 0; i < parser.getAttributeCount(); ++i) { 110 String attribute = parser.getAttributeName(i); 111 switch (attribute) { 112 case "mcc": 113 mcc = parser.getAttributeValue(i); 114 break; 115 case "mnc": 116 mnc = parser.getAttributeValue(i); 117 break; 118 case "gid1": 119 gid1 = parser.getAttributeValue(i); 120 break; 121 case "gid2": 122 gid2 = parser.getAttributeValue(i); 123 break; 124 case "spn": 125 spn = parser.getAttributeValue(i); 126 break; 127 case "imsi": 128 imsi = parser.getAttributeValue(i); 129 break; 130 default: 131 fail("Unknown attribute '" + attribute 132 + "' at " + parser.getPositionDescription()); 133 break; 134 } 135 } 136 mcc = (mcc != null) ? mcc : mccmnc.substring(0, 3); 137 mnc = (mnc != null) ? mnc : mccmnc.substring(3); 138 // check if there is a valid carrier id 139 int carrierId = getCarrierId(getInstrumentation().getTargetContext(), 140 new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2)); 141 if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 142 fail("unexpected carrier_config_mccmnc.xml with matching carrier id: " 143 + carrierId + ", please move to carrier_config_carrierid.xml"); 144 } 145 } 146 } 147 } 148 }); 149 } 150 151 /** 152 * Tests that the variable names in each XML file match actual keys in CarrierConfigManager. 153 */ testVariableNames()154 public void testVariableNames() { 155 final Set<String> varXmlNames = getCarrierConfigXmlNames(); 156 // organize them into sets by type or unknown 157 forEachConfigXml(new ParserChecker() { 158 public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException, 159 IOException { 160 int event; 161 while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) { 162 if (event == XmlPullParser.START_TAG) { 163 switch (parser.getName()) { 164 case "int-array": 165 case "string-array": 166 // string-array and int-array require the 'num' attribute 167 final String varNum = parser.getAttributeValue(null, "num"); 168 assertNotNull("No 'num' attribute in array: " 169 + parser.getPositionDescription(), varNum); 170 case "int": 171 case "long": 172 case "boolean": 173 case "string": 174 // NOTE: This doesn't check for other valid Bundle values, but it 175 // is limited to the key types in CarrierConfigManager. 176 final String varName = parser.getAttributeValue(null, "name"); 177 assertNotNull("No 'name' attribute: " 178 + parser.getPositionDescription(), varName); 179 assertTrue("Unknown variable: '" + varName 180 + "' at " + parser.getPositionDescription(), 181 varXmlNames.contains(varName)); 182 // TODO: Check that the type is correct. 183 break; 184 case "carrier_config_list": 185 case "item": 186 case "carrier_config": 187 // do nothing 188 break; 189 default: 190 fail("unexpected tag: '" + parser.getName() 191 + "' at " + parser.getPositionDescription()); 192 break; 193 } 194 } 195 } 196 } 197 }); 198 } 199 200 /** 201 * Utility for iterating over each XML document in the assets folder. 202 * 203 * This can be used with {@link #forEachConfigXml} to run checks on each XML document. 204 * {@link #check} should {@link #fail} if the test does not pass. 205 */ 206 private interface ParserChecker { check(XmlPullParser parser, String mccmnc)207 void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException, IOException; 208 } 209 210 /** 211 * Utility for iterating over each XML document in the assets folder. 212 */ forEachConfigXml(ParserChecker checker)213 private void forEachConfigXml(ParserChecker checker) { 214 AssetManager assetMgr = getInstrumentation().getTargetContext().getAssets(); 215 String mccmnc = null; 216 try { 217 String[] files = assetMgr.list(""); 218 assertNotNull("failed to list files", files); 219 assertTrue("no files", files.length > 0); 220 for (String fileName : files) { 221 try { 222 if (!fileName.startsWith("carrier_config_")) continue; 223 if (fileName.startsWith("carrier_config_mccmnc_")) { 224 mccmnc = fileName.substring("carrier_config_mccmnc_".length(), 225 fileName.indexOf(".xml")); 226 227 } 228 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 229 XmlPullParser parser = factory.newPullParser(); 230 parser.setInput(assetMgr.open(fileName), "utf-8"); 231 232 checker.check(parser, mccmnc); 233 234 } catch (Throwable e) { 235 throw new AssertionError("Problem in " + fileName + ": " + e.getMessage(), e); 236 } 237 } 238 // Check vendor.xml too 239 try { 240 Resources res = getInstrumentation().getTargetContext().getResources(); 241 checker.check(res.getXml(R.xml.vendor), mccmnc); 242 } catch (Throwable e) { 243 throw new AssertionError("Problem in vendor.xml: " + e.getMessage(), e); 244 } 245 } catch (IOException e) { 246 fail(e.toString()); 247 } 248 } 249 250 /** 251 * Get the set of config variable names, as used in XML files. 252 */ getCarrierConfigXmlNames()253 private Set<String> getCarrierConfigXmlNames() { 254 Set<String> names = new HashSet<>(); 255 // get values of all KEY_ members of CarrierConfigManager as well as its nested classes. 256 names.addAll(getCarrierConfigXmlNames(CarrierConfigManager.class)); 257 for (Class nested : CarrierConfigManager.class.getDeclaredClasses()) { 258 Log.i("CarrierConfigTest", nested.toString()); 259 if (Modifier.isStatic(nested.getModifiers())) { 260 names.addAll(getCarrierConfigXmlNames(nested)); 261 } 262 } 263 return names; 264 } 265 getCarrierConfigXmlNames(Class clazz)266 private Set<String> getCarrierConfigXmlNames(Class clazz) { 267 // get values of all KEY_ members of clazz 268 Field[] fields = clazz.getDeclaredFields(); 269 HashSet<String> varXmlNames = new HashSet<>(); 270 for (Field f : fields) { 271 if (!f.getName().startsWith("KEY_")) continue; 272 if (!Modifier.isStatic(f.getModifiers())) { 273 fail("non-static key in " + clazz.getName() + ":" + f.toString()); 274 } 275 try { 276 String value = (String) f.get(null); 277 varXmlNames.add(value); 278 } 279 catch (IllegalAccessException e) { 280 throw new AssertionError("Failed to get config key: " + e.getMessage(), e); 281 } 282 } 283 assertTrue("Found zero keys", varXmlNames.size() > 0); 284 return varXmlNames; 285 } 286 287 // helper function to get carrier id from carrierIdentifier getCarrierId(@onNull Context context, @NonNull CarrierIdentifier carrierIdentifier)288 private int getCarrierId(@NonNull Context context, 289 @NonNull CarrierIdentifier carrierIdentifier) { 290 try { 291 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 292 Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 293 List<String> args = new ArrayList<>(); 294 args.add(carrierIdentifier.getMcc() + carrierIdentifier.getMnc()); 295 if (carrierIdentifier.getGid1() != null) { 296 args.add(carrierIdentifier.getGid1()); 297 } 298 if (carrierIdentifier.getGid2() != null) { 299 args.add(carrierIdentifier.getGid2()); 300 } 301 if (carrierIdentifier.getImsi() != null) { 302 args.add(carrierIdentifier.getImsi()); 303 } 304 if (carrierIdentifier.getSpn() != null) { 305 args.add(carrierIdentifier.getSpn()); 306 } 307 try (Cursor cursor = context.getContentResolver().query( 308 Telephony.CarrierId.All.CONTENT_URI, 309 /* projection */ null, 310 /* selection */ Telephony.CarrierId.All.MCCMNC + "=? AND " 311 + Telephony.CarrierId.All.GID1 312 + ((carrierIdentifier.getGid1() == null) ? " is NULL" : "=?") + " AND " 313 + Telephony.CarrierId.All.GID2 314 + ((carrierIdentifier.getGid2() == null) ? " is NULL" : "=?") + " AND " 315 + Telephony.CarrierId.All.IMSI_PREFIX_XPATTERN 316 + ((carrierIdentifier.getImsi() == null) ? " is NULL" : "=?") + " AND " 317 + Telephony.CarrierId.All.SPN 318 + ((carrierIdentifier.getSpn() == null) ? " is NULL" : "=?"), 319 /* selectionArgs */ args.toArray(new String[args.size()]), null)) { 320 if (cursor != null) { 321 while (cursor.moveToNext()) { 322 return cursor.getInt(cursor.getColumnIndex(Telephony.CarrierId.CARRIER_ID)); 323 } 324 } 325 } 326 } catch (SecurityException e) { 327 fail("Should be able to access APIs protected by a permission apps cannot get"); 328 } finally { 329 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 330 } 331 return TelephonyManager.UNKNOWN_CARRIER_ID; 332 } 333 } 334