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