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