1 /*
2  * Copyright (C) 2017 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 package android.carrierapi.cts;
17 
18 import static com.google.common.truth.Truth.assertWithMessage;
19 
20 import static org.junit.Assert.assertThrows;
21 import static org.junit.Assert.fail;
22 
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.database.Cursor;
26 import android.database.sqlite.SQLiteException;
27 import android.net.Uri;
28 import android.provider.Telephony.Carriers;
29 import android.util.Log;
30 
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.util.Arrays;
38 import java.util.HashMap;
39 import java.util.Map;
40 
41 /**
42  * Unit tests for the APN database exposed by {@link Carriers}.
43  *
44  * <p>Test using `atest CtsCarrierApiTestCases:ApnDatabaseTest` or `make cts -j64 && cts-tradefed
45  * run cts -m CtsCarrierApiTestCases --test android.carrierapi.cts.ApnDatabaseTest`
46  */
47 @RunWith(AndroidJUnit4.class)
48 public class ApnDatabaseTest extends BaseCarrierApiTest {
49     private static final String TAG = "ApnDatabaseTest";
50 
51     private ContentResolver mContentResolver;
52 
53     private static final String NAME = "carrierName";
54     private static final String APN = "apn";
55     private static final String PROXY = "proxy";
56     private static final String PORT = "port";
57     private static final String MMSC = "mmsc";
58     private static final String MMSPROXY = "mmsproxy";
59     private static final String MMSPORT = "mmsport";
60     private static final String NUMERIC = "numeric";
61     private static final String USER = "user";
62     private static final String PASSWORD = "password";
63     private static final String AUTH_TYPE = "auth_type";
64     private static final String TYPE = "type";
65     private static final String PROTOCOL = "protocol";
66     private static final String ROAMING_PROTOCOL = "roaming_protocol";
67     private static final String CARRIER_ENABLED = "true";
68     private static final String NETWORK_TYPE_BITMASK = "0";
69     private static final String BEARER = "0";
70 
71     private static final Map<String, String> APN_MAP =
72             new HashMap<String, String>() {
73                 {
74                     put(Carriers.NAME, NAME);
75                     put(Carriers.APN, APN);
76                     put(Carriers.PROXY, PROXY);
77                     put(Carriers.PORT, PORT);
78                     put(Carriers.MMSC, MMSC);
79                     put(Carriers.MMSPROXY, MMSPROXY);
80                     put(Carriers.MMSPORT, MMSPORT);
81                     put(Carriers.NUMERIC, NUMERIC);
82                     put(Carriers.USER, USER);
83                     put(Carriers.PASSWORD, PASSWORD);
84                     put(Carriers.AUTH_TYPE, AUTH_TYPE);
85                     put(Carriers.TYPE, TYPE);
86                     put(Carriers.PROTOCOL, PROTOCOL);
87                     put(Carriers.ROAMING_PROTOCOL, ROAMING_PROTOCOL);
88                     put(Carriers.CARRIER_ENABLED, CARRIER_ENABLED);
89                     put(Carriers.NETWORK_TYPE_BITMASK, NETWORK_TYPE_BITMASK);
90                     put(Carriers.BEARER, BEARER);
91                 }
92             };
93 
94     // Faked network type bitmask and its compatible bearer bitmask.
95     private static final int NETWORK_TYPE_BITMASK_NUMBER = 1 << (13 - 1);
96     private static final int RIL_RADIO_TECHNOLOGY_BITMASK_NUMBER = 1 << (14 - 1);
97 
98     @Before
setUp()99     public void setUp() throws Exception {
100         mContentResolver = getContext().getContentResolver();
101     }
102 
103     /**
104      * Test inserting, querying, updating and deleting values in carriers table. Verify that the
105      * inserted values match the result of the query and are deleted.
106      */
107     @Test
testValidCase()108     public void testValidCase() {
109         Uri uri = Carriers.CONTENT_URI;
110         // Create A set of column_name/value pairs to add to the database.
111         ContentValues contentValues = makeDefaultContentValues();
112 
113         try {
114             // Insert the value into database.
115             Log.d(TAG, "testInsertCarriers Inserting contentValues: " + contentValues.toString());
116             Uri newUri = mContentResolver.insert(uri, contentValues);
117             assertWithMessage("Failed to insert to table").that(newUri).isNotNull();
118 
119             // Get the values in table.
120             final String selection = Carriers.NUMERIC + "=?";
121             String[] selectionArgs = {NUMERIC};
122             String[] apnProjection = APN_MAP.keySet().toArray(new String[APN_MAP.size()]);
123             Log.d(
124                     TAG,
125                     "testInsertCarriers query projection: "
126                             + Arrays.toString(apnProjection)
127                             + "\ntestInsertCarriers selection: "
128                             + selection
129                             + "\ntestInsertCarriers selectionArgs: "
130                             + Arrays.toString(selectionArgs));
131             Cursor cursor =
132                     mContentResolver.query(uri, apnProjection, selection, selectionArgs, null);
133 
134             // Verify that the inserted value match the results of the query
135             assertWithMessage("Failed to query the table").that(cursor).isNotNull();
136             assertWithMessage("Unexpected number of APNs returned by cursor")
137                     .that(cursor.getCount())
138                     .isEqualTo(1);
139             cursor.moveToFirst();
140             for (Map.Entry<String, String> entry : APN_MAP.entrySet()) {
141                 assertWithMessage("Unexpected value returned by cursor")
142                         .that(cursor.getString(cursor.getColumnIndex(entry.getKey())))
143                         .isEqualTo(entry.getValue());
144             }
145 
146             // update the apn
147             final String newApn = "newapn";
148             Log.d(TAG, "Update the APN field to: " + newApn);
149             contentValues.put(Carriers.APN, newApn);
150             final int updateCount =
151                     mContentResolver.update(uri, contentValues, selection, selectionArgs);
152             assertWithMessage("Unexpected number of rows updated").that(updateCount).isEqualTo(1);
153 
154             // Verify the updated value
155             cursor = mContentResolver.query(uri, apnProjection, selection, selectionArgs, null);
156             assertWithMessage("Failed to query the table").that(cursor).isNotNull();
157             assertWithMessage("Unexpected number of APNs returned by cursor")
158                     .that(cursor.getCount())
159                     .isEqualTo(1);
160             cursor.moveToFirst();
161             assertWithMessage("Unexpected value returned by cursor")
162                     .that(cursor.getString(cursor.getColumnIndex(Carriers.APN)))
163                     .isEqualTo(newApn);
164 
165             // delete test content
166             final String selectionToDelete = Carriers.NUMERIC + "=?";
167             String[] selectionArgsToDelete = {NUMERIC};
168             Log.d(
169                     TAG,
170                     "testInsertCarriers deleting selection: "
171                             + selectionToDelete
172                             + "testInsertCarriers selectionArgs: "
173                             + Arrays.toString(selectionArgs));
174             int numRowsDeleted =
175                     mContentResolver.delete(uri, selectionToDelete, selectionArgsToDelete);
176             assertWithMessage("Unexpected number of rows deleted")
177                     .that(numRowsDeleted)
178                     .isEqualTo(1);
179 
180             // verify that deleted values are gone
181             cursor = mContentResolver.query(uri, apnProjection, selection, selectionArgs, null);
182             assertWithMessage("Unexpected number of rows deleted")
183                     .that(cursor.getCount())
184                     .isEqualTo(0);
185         } catch (SecurityException e) {
186             fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
187         }
188     }
189 
190     @Test
testQueryConflictCase()191     public void testQueryConflictCase() {
192         String invalidColumn = "random";
193         Uri uri = Carriers.CONTENT_URI;
194         // Create a set of column_name/value pairs to add to the database.
195         ContentValues contentValues = new ContentValues();
196         contentValues.put(Carriers.NAME, NAME);
197         contentValues.put(Carriers.APN, APN);
198         contentValues.put(Carriers.PORT, PORT);
199         contentValues.put(Carriers.PROTOCOL, PROTOCOL);
200         contentValues.put(Carriers.NUMERIC, NUMERIC);
201 
202         try {
203             // Insert the value into database.
204             Log.d(TAG, "testInsertCarriers Inserting contentValues: " + contentValues.toString());
205             Uri newUri = mContentResolver.insert(uri, contentValues);
206             assertWithMessage("Failed to insert to table").that(newUri).isNotNull();
207 
208             // Try to get the value with invalid selection
209             final String[] testProjection = {
210                 Carriers.NAME, Carriers.APN, Carriers.PORT, Carriers.PROTOCOL, Carriers.NUMERIC,
211             };
212             final String selection = invalidColumn + "=?";
213             String[] selectionArgs = {invalidColumn};
214             Log.d(
215                     TAG,
216                     "testInsertCarriers query projection: "
217                             + Arrays.toString(testProjection)
218                             + "\ntestInsertCarriers selection: "
219                             + selection
220                             + "\ntestInsertCarriers selectionArgs: "
221                             + Arrays.toString(selectionArgs));
222             Cursor cursor =
223                     mContentResolver.query(uri, testProjection, selection, selectionArgs, null);
224             assertWithMessage("Failed to query the table").that(cursor).isNull();
225 
226             // delete test content
227             final String selectionToDelete = Carriers.NAME + "=?";
228             String[] selectionArgsToDelete = {NAME};
229             Log.d(
230                     TAG,
231                     "testInsertCarriers deleting selection: "
232                             + selectionToDelete
233                             + "testInsertCarriers selectionArgs: "
234                             + Arrays.toString(selectionArgs));
235             int numRowsDeleted =
236                     mContentResolver.delete(uri, selectionToDelete, selectionArgsToDelete);
237             assertWithMessage("Unexpected number of rows deleted")
238                     .that(numRowsDeleted)
239                     .isEqualTo(1);
240 
241             // verify that deleted values are gone
242             cursor =
243                     mContentResolver.query(
244                             uri, testProjection, selectionToDelete, selectionArgsToDelete, null);
245             assertWithMessage("Unexpected number of rows deleted")
246                     .that(cursor.getCount())
247                     .isEqualTo(0);
248         } catch (SecurityException e) {
249             fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
250         }
251     }
252 
253     @Test
testUpdateConflictCase()254     public void testUpdateConflictCase() {
255         Uri uri = Carriers.CONTENT_URI;
256         // Create a set of column_name/value pairs to add to the database.
257         ContentValues contentValues = new ContentValues();
258         contentValues.put(Carriers.NAME, NAME);
259         contentValues.put(Carriers.APN, APN);
260         contentValues.put(Carriers.PORT, PORT);
261         contentValues.put(Carriers.PROTOCOL, PROTOCOL);
262         contentValues.put(Carriers.NUMERIC, NUMERIC);
263 
264         try {
265             // Insert the value into database.
266             Log.d(TAG, "testInsertCarriers Inserting contentValues: " + contentValues.toString());
267             Uri newUri = mContentResolver.insert(uri, contentValues);
268             assertWithMessage("Failed to insert to table").that(newUri).isNotNull();
269 
270             // Try to get the value with invalid selection
271             final String[] testProjection = {
272                 Carriers.NAME, Carriers.APN, Carriers.PORT, Carriers.PROTOCOL, Carriers.NUMERIC,
273             };
274             String selection = Carriers.NAME + "=?";
275             String[] selectionArgs = {NAME};
276             Log.d(
277                     TAG,
278                     "testInsertCarriers query projection: "
279                             + Arrays.toString(testProjection)
280                             + "\ntestInsertCarriers selection: "
281                             + selection
282                             + "\ntestInsertCarriers selectionArgs: "
283                             + Arrays.toString(selectionArgs));
284             Cursor cursor =
285                     mContentResolver.query(uri, testProjection, selection, selectionArgs, null);
286             assertWithMessage("Unexpected number of APNs returned by cursor")
287                     .that(cursor.getCount())
288                     .isEqualTo(1);
289 
290             // Update the table with invalid column
291             String invalidColumn = "random";
292             contentValues.put(invalidColumn, invalidColumn);
293             // Expected: If there's no such a column, an exception will be thrown and
294             // ActivityManager will kill this process shortly.
295             assertThrows(
296                     SQLiteException.class,
297                     () -> mContentResolver.update(uri, contentValues, selection, selectionArgs));
298 
299             // delete test content
300             final String selectionToDelete = Carriers.NAME + "=?";
301             String[] selectionArgsToDelete = {NAME};
302             Log.d(
303                     TAG,
304                     "testInsertCarriers deleting selection: "
305                             + selectionToDelete
306                             + "testInsertCarriers selectionArgs: "
307                             + Arrays.toString(selectionArgs));
308             int numRowsDeleted =
309                     mContentResolver.delete(uri, selectionToDelete, selectionArgsToDelete);
310             assertWithMessage("Unexpected number of rows deleted")
311                     .that(numRowsDeleted)
312                     .isEqualTo(1);
313 
314             // verify that deleted values are gone
315             cursor =
316                     mContentResolver.query(
317                             uri, testProjection, selectionToDelete, selectionArgsToDelete, null);
318             assertWithMessage("Unexpected number of rows deleted")
319                     .that(cursor.getCount())
320                     .isEqualTo(0);
321         } catch (SecurityException e) {
322             fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
323         }
324     }
325 
326     @Test
testDeleteConflictCase()327     public void testDeleteConflictCase() {
328         String invalidColumn = "random";
329         Uri uri = Carriers.CONTENT_URI;
330         // Create a set of column_name/value pairs to add to the database.
331         ContentValues contentValues = new ContentValues();
332         contentValues.put(Carriers.NAME, NAME);
333         contentValues.put(Carriers.APN, APN);
334         contentValues.put(Carriers.PORT, PORT);
335         contentValues.put(Carriers.PROTOCOL, PROTOCOL);
336         contentValues.put(Carriers.NUMERIC, NUMERIC);
337 
338         try {
339             // Insert the value into database.
340             Log.d(TAG, "testInsertCarriers Inserting contentValues: " + contentValues.toString());
341             Uri newUri = mContentResolver.insert(uri, contentValues);
342             assertWithMessage("Failed to insert to table").that(newUri).isNotNull();
343 
344             // Get the values in table.
345             final String[] testProjection = {
346                 Carriers.NAME, Carriers.APN, Carriers.PORT, Carriers.PROTOCOL, Carriers.NUMERIC,
347             };
348             String selection = Carriers.NAME + "=?";
349             String[] selectionArgs = {NAME};
350             Log.d(
351                     TAG,
352                     "testInsertCarriers query projection: "
353                             + Arrays.toString(testProjection)
354                             + "\ntestInsertCarriers selection: "
355                             + selection
356                             + "\ntestInsertCarriers selectionArgs: "
357                             + Arrays.toString(selectionArgs));
358             Cursor cursor =
359                     mContentResolver.query(uri, testProjection, selection, selectionArgs, null);
360             assertWithMessage("Unexpected number of APNs returned by cursor")
361                     .that(cursor.getCount())
362                     .isEqualTo(1);
363 
364             // try to delete with invalid selection
365             String invalidSelectionToDelete = invalidColumn + "=?";
366             String[] invalidSelectionArgsToDelete = {invalidColumn};
367             Log.d(
368                     TAG,
369                     "testInsertCarriers deleting selection: "
370                             + invalidSelectionToDelete
371                             + "testInsertCarriers selectionArgs: "
372                             + Arrays.toString(selectionArgs));
373 
374             // Expected: If there's no such a column, an exception will be thrown and
375             // ActivityManager will kill this process shortly.
376             assertThrows(
377                     SQLiteException.class,
378                     () ->
379                             mContentResolver.delete(
380                                     uri, invalidSelectionToDelete, invalidSelectionArgsToDelete));
381 
382             // verify that deleted value is still there
383             selection = Carriers.NAME + "=?";
384             selectionArgs[0] = NAME;
385             cursor = mContentResolver.query(uri, testProjection, selection, selectionArgs, null);
386             assertWithMessage("Unexpected number of APNs returned by cursor")
387                     .that(cursor.getCount())
388                     .isEqualTo(1);
389 
390             // delete test content
391             String selectionToDelete = Carriers.NAME + "=?";
392             String[] selectionArgsToDelete = {NAME};
393             Log.d(
394                     TAG,
395                     "testInsertCarriers deleting selection: "
396                             + selectionToDelete
397                             + "testInsertCarriers selectionArgs: "
398                             + Arrays.toString(selectionArgs));
399             int numRowsDeleted =
400                     mContentResolver.delete(uri, selectionToDelete, selectionArgsToDelete);
401             assertWithMessage("Unexpected number of rows deleted")
402                     .that(numRowsDeleted)
403                     .isEqualTo(1);
404 
405             // verify that deleted values are gone
406             cursor = mContentResolver.query(uri, testProjection, selection, selectionArgs, null);
407             assertWithMessage("Unexpected number of rows deleted")
408                     .that(cursor.getCount())
409                     .isEqualTo(0);
410         } catch (SecurityException e) {
411             fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
412         }
413     }
414 
makeDefaultContentValues()415     private ContentValues makeDefaultContentValues() {
416         ContentValues contentValues = new ContentValues();
417         for (Map.Entry<String, String> entry : APN_MAP.entrySet()) {
418             contentValues.put(entry.getKey(), entry.getValue());
419         }
420         return contentValues;
421     }
422 }
423