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