1 /*
2  * Copyright (C) 2023 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.telephonyprovider.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 import static org.junit.Assume.assumeTrue;
23 
24 import static java.util.Map.entry;
25 
26 import android.content.ContentResolver;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.provider.Telephony.Carriers;
33 import android.telephony.TelephonyManager;
34 import android.util.Log;
35 
36 import androidx.test.InstrumentationRegistry;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.android.compatibility.common.util.ApiTest;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 
46 import java.util.Map;
47 
48 /**
49  * Tests for the APN database exposed by {@link Carriers}.
50  */
51 @ApiTest(apis = {"android.provider.Telephony.Carriers#CONTENT_URI"})
52 @RunWith(AndroidJUnit4.class)
53 public class ApnTest {
54     private static final String TAG = "ApnTest";
55 
56     private static final Uri CARRIER_TABLE_URI = Carriers.CONTENT_URI;
57 
58     private static final String NAME = "carrierName";
59     private static final String APN = "apn";
60     private static final String PROXY = "proxy";
61     private static final String PORT = "port";
62     private static final String MMSC = "mmsc";
63     private static final String MMSPROXY = "mmsproxy";
64     private static final String MMSPORT = "mmsport";
65     private static final String USER = "user";
66     private static final String PASSWORD = "password";
67     private static final String AUTH_TYPE = "auth_type";
68     private static final String TYPE = "type";
69     private static final String PROTOCOL = "protocol";
70     private static final String ROAMING_PROTOCOL = "roaming_protocol";
71     private static final String CARRIER_ENABLED = "true";
72     private static final String NETWORK_TYPE_BITMASK = "0";
73     private static final String BEARER = "0";
74     private static final String CARRIER_ID = String.valueOf(TelephonyManager.UNKNOWN_CARRIER_ID);
75 
76     private static final Map<String, String> APN_MAP =
77             Map.ofEntries(
78                     entry(Carriers.NAME, NAME),
79                     entry(Carriers.APN, APN),
80                     entry(Carriers.PROXY, PROXY),
81                     entry(Carriers.PORT, PORT),
82                     entry(Carriers.MMSC, MMSC),
83                     entry(Carriers.MMSPROXY, MMSPROXY),
84                     entry(Carriers.MMSPORT, MMSPORT),
85                     entry(Carriers.USER, USER),
86                     entry(Carriers.PASSWORD, PASSWORD),
87                     entry(Carriers.AUTH_TYPE, AUTH_TYPE),
88                     entry(Carriers.TYPE, TYPE),
89                     entry(Carriers.PROTOCOL, PROTOCOL),
90                     entry(Carriers.ROAMING_PROTOCOL, ROAMING_PROTOCOL),
91                     entry(Carriers.CARRIER_ENABLED, CARRIER_ENABLED),
92                     entry(Carriers.NETWORK_TYPE_BITMASK, NETWORK_TYPE_BITMASK),
93                     entry(Carriers.BEARER, BEARER),
94                     entry(Carriers.CARRIER_ID, CARRIER_ID));
95 
96     // make sure the numeric is empty aka default value
97     private static final String TEST_APN_SELECTION = Carriers.NUMERIC + "= '' AND "
98             + Carriers.CARRIER_ID + "=?";
99     private static final String[] TEST_APN_SELECTION_ARGS = {CARRIER_ID};
100     private static final String[] TEST_APN_PROJECTION =
101             APN_MAP.keySet().toArray(new String[APN_MAP.size()]);
102 
103     private Context mContext;
104     private ContentResolver mContentResolver;
105 
106     @Before
setUp()107     public void setUp() throws Exception {
108         mContext = InstrumentationRegistry.getInstrumentation().getContext();
109         assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
110 
111         mContentResolver = mContext.getContentResolver();
112     }
113 
114     @After
tearDown()115     public void tearDown() throws Exception {
116         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
117             return;
118         }
119 
120         // Ensures APNs are deleted in case a test threw.
121         InstrumentationRegistry.getInstrumentation()
122                 .getUiAutomation()
123                 .adoptShellPermissionIdentity();
124         try {
125             // Delete the test APN (potentially redundant for deletion test).
126             mContentResolver.delete(CARRIER_TABLE_URI, TEST_APN_SELECTION, TEST_APN_SELECTION_ARGS);
127         } finally {
128             InstrumentationRegistry.getInstrumentation()
129                     .getUiAutomation()
130                     .dropShellPermissionIdentity();
131         }
132     }
133 
134     /* The CTS tests are intended to test APN insertion with {@link
135      * android.Manifest.permission.READ_PHONE_STATE} and makes use of {@link
136      * android.Manifest.permission.WRITE_APN_SETTINGS} instead of carrier privileges. This covers
137      * a class of errors where APIs that require READ_PHONE_STATE are called during APN updates.
138      * In these cases, if a caller has READ_PHONE_STATE, it is important to ensure that the binder
139      * identity is appropriately cleared or handled. Otherwise, the calling UID may not match
140      * the calling package during permission checks.
141      */
142     @Test
testPermissionCheckForApnInsertion_success()143     public void testPermissionCheckForApnInsertion_success() {
144         try {
145             // Insert the test APN.
146             insertTestApnAndValidateInsertion(/* grantPermission= */ true);
147         } catch (SecurityException e) {
148             fail(
149                     "Test failed due to security exception. Permissions may be incorrectly checked"
150                             + " or managed. "
151                             + Log.getStackTraceString(e));
152         } catch (Exception e) {
153             fail("Test failed due to exception. " + Log.getStackTraceString(e));
154         }
155     }
156 
157     @Test
testPermissionCheckForApnInsertion_noWriteApnSettings()158     public void testPermissionCheckForApnInsertion_noWriteApnSettings() {
159         assertThrows(
160                 SecurityException.class,
161                 () -> insertTestApnAndValidateInsertion(/* grantPermission= */ false));
162     }
163 
164     @Test
testPermissionCheckForApnUpdate_success()165     public void testPermissionCheckForApnUpdate_success() {
166         try {
167             // Insert the test APN.
168             insertTestApnAndValidateInsertion(/* grantPermission= */ true);
169             grantWriteApnSettings();
170             // Create an APN entry to update.
171             ContentValues contentValues = makeDefaultContentValues();
172 
173             // Update the APN.
174             final String newApn = "newapn";
175             contentValues.put(Carriers.APN, newApn);
176             final int updateCount =
177                     mContentResolver.update(
178                             CARRIER_TABLE_URI,
179                             contentValues,
180                             TEST_APN_SELECTION,
181                             TEST_APN_SELECTION_ARGS);
182 
183             assertWithMessage("Unexpected number of rows updated").that(updateCount).isEqualTo(1);
184             // Verify the updated value.
185             Cursor cursor = queryTestApn();
186             assertWithMessage("Failed to query the table").that(cursor).isNotNull();
187             assertWithMessage("Unexpected number of APNs returned by cursor")
188                     .that(cursor.getCount())
189                     .isEqualTo(1);
190             cursor.moveToFirst();
191             assertWithMessage("Unexpected value returned by cursor")
192                     .that(cursor.getString(cursor.getColumnIndex(Carriers.APN)))
193                     .isEqualTo(newApn);
194         } catch (SecurityException e) {
195             fail(
196                     "Test failed due to security exception. Permissions may be incorrectly checked"
197                             + " or managed. "
198                             + Log.getStackTraceString(e));
199         } catch (Exception e) {
200             fail("Test failed due to exception. " + Log.getStackTraceString(e));
201         }
202     }
203 
204     @Test
testPermissionCheckForApnUpdate_noWriteApnSettings()205     public void testPermissionCheckForApnUpdate_noWriteApnSettings() {
206         try {
207             // Insert the test APN.
208             insertTestApnAndValidateInsertion(/* grantPermission= */ true);
209             // Create an APN entry to update.
210             ContentValues contentValues = makeDefaultContentValues();
211 
212             // Attempt to update the APN without WRITE_APN_SETTINGS.
213             contentValues.put(Carriers.APN, "newapn");
214             assertThrows(
215                     SecurityException.class,
216                     () ->
217                             mContentResolver.update(
218                                     CARRIER_TABLE_URI,
219                                     contentValues,
220                                     TEST_APN_SELECTION,
221                                     TEST_APN_SELECTION_ARGS));
222         } catch (SecurityException e) {
223             fail(
224                     "Test failed due to security exception. Permissions may be incorrectly checked"
225                             + " or managed. "
226                             + Log.getStackTraceString(e));
227         } catch (Exception e) {
228             fail("Test failed due to exception. " + Log.getStackTraceString(e));
229         }
230     }
231 
232     @Test
testPermissionCheckForApnDeletion_success()233     public void testPermissionCheckForApnDeletion_success() {
234         try {
235             insertTestApnAndValidateInsertion(/* grantPermission= */ true);
236             grantWriteApnSettings();
237 
238             // Delete the APN.
239             int numberOfRowsDeleted =
240                     mContentResolver.delete(
241                             CARRIER_TABLE_URI, TEST_APN_SELECTION, TEST_APN_SELECTION_ARGS);
242 
243             assertWithMessage("Unexpected number of rows deleted")
244                     .that(numberOfRowsDeleted)
245                     .isEqualTo(1);
246             // Verify that deleted values are gone.
247             Cursor cursor = queryTestApn();
248             assertWithMessage("Unexpected number of rows deleted")
249                     .that(cursor.getCount())
250                     .isEqualTo(0);
251         } catch (SecurityException e) {
252             fail(
253                     "Test failed due to security exception. Permissions may be incorrectly checked"
254                             + " or managed. "
255                             + Log.getStackTraceString(e));
256         } catch (Exception e) {
257             fail("Test failed due to exception. " + Log.getStackTraceString(e));
258         }
259     }
260 
261     @Test
testPermissionCheckForApnDeletion_noWriteApnSettings()262     public void testPermissionCheckForApnDeletion_noWriteApnSettings() {
263         try {
264             insertTestApnAndValidateInsertion(/* grantPermission= */ true);
265 
266             // Attempt to delete the APN without WRITE_APN_SETTINGS.
267             assertThrows(
268                     SecurityException.class,
269                     () ->
270                             mContentResolver.delete(
271                                     CARRIER_TABLE_URI,
272                                     TEST_APN_SELECTION,
273                                     TEST_APN_SELECTION_ARGS));
274         } catch (SecurityException e) {
275             fail(
276                     "Test failed due to security exception. Permissions may be incorrectly checked"
277                             + " or managed. "
278                             + Log.getStackTraceString(e));
279         } catch (Exception e) {
280             fail("Test failed due to exception. " + Log.getStackTraceString(e));
281         }
282     }
283 
284     /**
285      * Verify that the test APN setting can be inserted, queried, and validated
286      * by {@link Carriers#CARRIER_ID} without {@link Carriers#NUMERIC} when
287      * the permission is granted
288      *
289      * @param grantPermission whether to grant WRITE_APN_SETTINGS prior to insertion
290      */
insertTestApnAndValidateInsertion(boolean grantPermission)291     private void insertTestApnAndValidateInsertion(boolean grantPermission) throws Exception {
292         if (grantPermission) {
293             grantWriteApnSettings();
294         }
295         // Create a set of column_name/value pairs to add to the database.
296         ContentValues contentValues = makeDefaultContentValues();
297 
298         // Insert the value into database. Without permissions, this is expected to throw.
299         Uri newUri = mContentResolver.insert(CARRIER_TABLE_URI, contentValues);
300 
301         assertWithMessage("Failed to insert to table").that(newUri).isNotNull();
302         Cursor cursor = queryTestApn();
303         // Verify that the inserted value match the results of the query.
304         assertWithMessage("Failed to query the table").that(cursor).isNotNull();
305         assertWithMessage("Unexpected number of APNs returned by cursor")
306                 .that(cursor.getCount())
307                 .isEqualTo(1);
308         cursor.moveToFirst();
309         for (Map.Entry<String, String> entry : APN_MAP.entrySet()) {
310             assertWithMessage("Unexpected value returned by cursor")
311                     .that(cursor.getString(cursor.getColumnIndex(entry.getKey())))
312                     .isEqualTo(entry.getValue());
313         }
314 
315         if (grantPermission) {
316             InstrumentationRegistry.getInstrumentation()
317                     .getUiAutomation()
318                     .dropShellPermissionIdentity();
319         }
320     }
321 
322     /** Grants WRITE_APN_SETTINGS through shell identity. */
grantWriteApnSettings()323     private void grantWriteApnSettings() {
324         InstrumentationRegistry.getInstrumentation()
325                 .getUiAutomation()
326                 .adoptShellPermissionIdentity("android.permission.WRITE_APN_SETTINGS");
327     }
328 
329     /** Returns a cursor corresponding to the test APN value. */
queryTestApn()330     private Cursor queryTestApn() {
331         return mContentResolver.query(
332                 CARRIER_TABLE_URI,
333                 TEST_APN_PROJECTION,
334                 TEST_APN_SELECTION,
335                 TEST_APN_SELECTION_ARGS,
336                 /* sortOrder= */ null);
337     }
338 
makeDefaultContentValues()339     private ContentValues makeDefaultContentValues() {
340         ContentValues contentValues = new ContentValues();
341         APN_MAP.entrySet().stream()
342                 .forEach(entry -> contentValues.put(entry.getKey(), entry.getValue()));
343         return contentValues;
344     }
345 }
346