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