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