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