blob: 4b78fd69af89fc25003d4e4ea7ae3f9b304e098e [file] [log] [blame]
package com.android.carrierconfig;
import android.Manifest;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.PersistableBundle;
import android.provider.Telephony;
import android.service.carrier.CarrierIdentifier;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import junit.framework.AssertionFailedError;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
public class CarrierConfigTest extends InstrumentationTestCase {
private static final String TAG = "CarrierConfigTest";
/**
* Iterate over all XML files in assets/ and ensure they parse without error.
*/
public void testAllFilesParse() {
forEachConfigXml(new ParserChecker() {
public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException,
IOException {
PersistableBundle b = DefaultCarrierConfigService.readConfigFromXml(parser,
new CarrierIdentifier("001", "001", "Test", "001001123456789", "", ""));
assertNotNull("got null bundle", b);
}
});
}
/**
* Check that the config bundles in XML files have valid filter attributes.
* This checks the attribute names only.
*/
public void testFilterValidAttributes() {
forEachConfigXml(new ParserChecker() {
public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException,
IOException {
int event;
while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
if (event == XmlPullParser.START_TAG
&& "carrier_config".equals(parser.getName())) {
for (int i = 0; i < parser.getAttributeCount(); ++i) {
String attribute = parser.getAttributeName(i);
switch (attribute) {
case "mcc":
case "mnc":
case "gid1":
case "gid2":
case "spn":
case "imsi":
case "device":
case "cid":
case "name":
break;
default:
fail("Unknown attribute '" + attribute
+ "' at " + parser.getPositionDescription());
break;
}
}
}
}
}
});
}
/**
* Check that XML files named after mccmnc are those without matching carrier id.
* If there is a matching carrier id, all configurations should move to carrierid.xml which
* has a higher matching priority than mccmnc.xml
*/
public void testCarrierConfigFileNaming() {
forEachConfigXml(new ParserChecker() {
public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException,
IOException {
if (mccmnc == null) {
// only check file named after mccmnc
return;
}
int event;
while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
if (event == XmlPullParser.START_TAG
&& "carrier_config".equals(parser.getName())) {
String mcc = null;
String mnc = null;
String spn = null;
String gid1 = null;
String gid2 = null;
String imsi = null;
for (int i = 0; i < parser.getAttributeCount(); ++i) {
String attribute = parser.getAttributeName(i);
switch (attribute) {
case "mcc":
mcc = parser.getAttributeValue(i);
break;
case "mnc":
mnc = parser.getAttributeValue(i);
break;
case "gid1":
gid1 = parser.getAttributeValue(i);
break;
case "gid2":
gid2 = parser.getAttributeValue(i);
break;
case "spn":
spn = parser.getAttributeValue(i);
break;
case "imsi":
imsi = parser.getAttributeValue(i);
break;
default:
fail("Unknown attribute '" + attribute
+ "' at " + parser.getPositionDescription());
break;
}
}
mcc = (mcc != null) ? mcc : mccmnc.substring(0, 3);
mnc = (mnc != null) ? mnc : mccmnc.substring(3);
// check if there is a valid carrier id
int carrierId = getCarrierId(getInstrumentation().getTargetContext(),
new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
fail("unexpected carrier_config_mccmnc.xml with matching carrier id: "
+ carrierId + ", please move to carrier_config_carrierid.xml");
}
}
}
}
});
}
/**
* Tests that the variable names in each XML file match actual keys in CarrierConfigManager.
*/
public void testVariableNames() {
final Set<String> varXmlNames = getCarrierConfigXmlNames();
// organize them into sets by type or unknown
forEachConfigXml(new ParserChecker() {
public void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException,
IOException {
int event;
while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
if (event == XmlPullParser.START_TAG) {
switch (parser.getName()) {
case "int-array":
case "string-array":
// string-array and int-array require the 'num' attribute
final String varNum = parser.getAttributeValue(null, "num");
assertNotNull("No 'num' attribute in array: "
+ parser.getPositionDescription(), varNum);
case "int":
case "long":
case "boolean":
case "string":
// NOTE: This doesn't check for other valid Bundle values, but it
// is limited to the key types in CarrierConfigManager.
final String varName = parser.getAttributeValue(null, "name");
assertNotNull("No 'name' attribute: "
+ parser.getPositionDescription(), varName);
assertTrue("Unknown variable: '" + varName
+ "' at " + parser.getPositionDescription(),
varXmlNames.contains(varName));
// TODO: Check that the type is correct.
break;
case "carrier_config_list":
case "item":
case "carrier_config":
// do nothing
break;
default:
fail("unexpected tag: '" + parser.getName()
+ "' at " + parser.getPositionDescription());
break;
}
}
}
}
});
}
/**
* Utility for iterating over each XML document in the assets folder.
*
* This can be used with {@link #forEachConfigXml} to run checks on each XML document.
* {@link #check} should {@link #fail} if the test does not pass.
*/
private interface ParserChecker {
void check(XmlPullParser parser, String mccmnc) throws XmlPullParserException, IOException;
}
/**
* Utility for iterating over each XML document in the assets folder.
*/
private void forEachConfigXml(ParserChecker checker) {
AssetManager assetMgr = getInstrumentation().getTargetContext().getAssets();
String mccmnc = null;
try {
String[] files = assetMgr.list("");
assertNotNull("failed to list files", files);
assertTrue("no files", files.length > 0);
for (String fileName : files) {
try {
if (!fileName.startsWith("carrier_config_")) continue;
if (fileName.startsWith("carrier_config_mccmnc_")) {
mccmnc = fileName.substring("carrier_config_mccmnc_".length(),
fileName.indexOf(".xml"));
}
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
parser.setInput(assetMgr.open(fileName), "utf-8");
checker.check(parser, mccmnc);
} catch (Throwable e) {
throw new AssertionError("Problem in " + fileName + ": " + e.getMessage(), e);
}
}
// Check vendor.xml too
try {
Resources res = getInstrumentation().getTargetContext().getResources();
checker.check(res.getXml(R.xml.vendor), mccmnc);
} catch (Throwable e) {
throw new AssertionError("Problem in vendor.xml: " + e.getMessage(), e);
}
} catch (IOException e) {
fail(e.toString());
}
}
/**
* Get the set of config variable names, as used in XML files.
*/
private Set<String> getCarrierConfigXmlNames() {
Set<String> names = new HashSet<>();
// get values of all KEY_ members of CarrierConfigManager as well as its nested classes.
names.addAll(getCarrierConfigXmlNames(CarrierConfigManager.class));
for (Class nested : CarrierConfigManager.class.getDeclaredClasses()) {
Log.i("CarrierConfigTest", nested.toString());
if (Modifier.isStatic(nested.getModifiers())) {
names.addAll(getCarrierConfigXmlNames(nested));
}
}
return names;
}
private Set<String> getCarrierConfigXmlNames(Class clazz) {
// get values of all KEY_ members of clazz
Field[] fields = clazz.getDeclaredFields();
HashSet<String> varXmlNames = new HashSet<>();
for (Field f : fields) {
if (!f.getName().startsWith("KEY_")) continue;
if (!Modifier.isStatic(f.getModifiers())) {
fail("non-static key in " + clazz.getName() + ":" + f.toString());
}
try {
String value = (String) f.get(null);
varXmlNames.add(value);
}
catch (IllegalAccessException e) {
throw new AssertionError("Failed to get config key: " + e.getMessage(), e);
}
}
assertTrue("Found zero keys", varXmlNames.size() > 0);
return varXmlNames;
}
// helper function to get carrier id from carrierIdentifier
private int getCarrierId(@NonNull Context context,
@NonNull CarrierIdentifier carrierIdentifier) {
try {
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
List<String> args = new ArrayList<>();
args.add(carrierIdentifier.getMcc() + carrierIdentifier.getMnc());
if (carrierIdentifier.getGid1() != null) {
args.add(carrierIdentifier.getGid1());
}
if (carrierIdentifier.getGid2() != null) {
args.add(carrierIdentifier.getGid2());
}
if (carrierIdentifier.getImsi() != null) {
args.add(carrierIdentifier.getImsi());
}
if (carrierIdentifier.getSpn() != null) {
args.add(carrierIdentifier.getSpn());
}
try (Cursor cursor = context.getContentResolver().query(
Telephony.CarrierId.All.CONTENT_URI,
/* projection */ null,
/* selection */ Telephony.CarrierId.All.MCCMNC + "=? AND "
+ Telephony.CarrierId.All.GID1
+ ((carrierIdentifier.getGid1() == null) ? " is NULL" : "=?") + " AND "
+ Telephony.CarrierId.All.GID2
+ ((carrierIdentifier.getGid2() == null) ? " is NULL" : "=?") + " AND "
+ Telephony.CarrierId.All.IMSI_PREFIX_XPATTERN
+ ((carrierIdentifier.getImsi() == null) ? " is NULL" : "=?") + " AND "
+ Telephony.CarrierId.All.SPN
+ ((carrierIdentifier.getSpn() == null) ? " is NULL" : "=?"),
/* selectionArgs */ args.toArray(new String[args.size()]), null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
return cursor.getInt(cursor.getColumnIndex(Telephony.CarrierId.CARRIER_ID));
}
}
}
} catch (SecurityException e) {
fail("Should be able to access APIs protected by a permission apps cannot get");
} finally {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
return TelephonyManager.UNKNOWN_CARRIER_ID;
}
}