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 17 package android.telephony.cts; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.fail; 22 import static org.junit.Assume.assumeFalse; 23 import static org.junit.Assume.assumeTrue; 24 25 import static java.util.concurrent.TimeUnit.MILLISECONDS; 26 27 import android.content.ContentResolver; 28 import android.content.ContentValues; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.database.Cursor; 32 import android.net.Uri; 33 import android.platform.test.annotations.AppModeNonSdkSandbox; 34 import android.platform.test.flag.junit.CheckFlagsRule; 35 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 36 import android.provider.Telephony.Carriers; 37 import android.telephony.AccessNetworkConstants; 38 import android.telephony.PreciseDataConnectionState; 39 import android.telephony.SubscriptionManager; 40 import android.telephony.TelephonyCallback; 41 import android.telephony.TelephonyManager; 42 import android.telephony.data.ApnSetting; 43 import android.text.TextUtils; 44 45 import androidx.test.InstrumentationRegistry; 46 import androidx.test.runner.AndroidJUnit4; 47 48 import com.android.compatibility.common.util.ApiTest; 49 50 import org.junit.After; 51 import org.junit.Before; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.Executor; 61 62 /** 63 * Ensures that APNs that use carrier ID instead of legacy identifiers such as MCCMNC, MVNO type and 64 * match data are able to establish a data connection. 65 */ 66 @ApiTest( 67 apis = { 68 "android.provider.Telephony.Carriers#CONTENT_URI", 69 "android.provider.Telephony.Carriers#CARRIER_ID" 70 }) 71 @RunWith(AndroidJUnit4.class) 72 public class ApnCarrierIdTest { 73 @Rule 74 public final CheckFlagsRule mCheckFlagsRule = 75 DeviceFlagsValueProvider.createCheckFlagsRule(); 76 77 private static final Uri CARRIER_TABLE_URI = Carriers.CONTENT_URI; 78 /** 79 * A base selection string of columns we use to query an APN in the APN database. This excludes 80 * the numeric/carrier ID. 81 * 82 * <p>While it would be ideal to include the Carrier.TYPE here, the ordering of APN types 83 * generated from ApnSetting may not match the ordering when we query the type from the APN 84 * database, which makes it more non-trivial to query using type. 85 */ 86 private static final String BASE_APN_SELECTION_COLUMNS = 87 generateSelectionString( 88 List.of( 89 Carriers.NAME, 90 Carriers.APN, 91 Carriers.PROTOCOL, 92 Carriers.ROAMING_PROTOCOL, 93 Carriers.NETWORK_TYPE_BITMASK)); 94 95 private static final String APN_SELECTION_STRING_WITH_NUMERIC = 96 BASE_APN_SELECTION_COLUMNS + "AND " + Carriers.NUMERIC + "=?"; 97 private static final String APN_SELECTION_STRING_WITH_CARRIER_ID = 98 BASE_APN_SELECTION_COLUMNS + "AND " + Carriers.CARRIER_ID + "=?"; 99 100 // The wait time is padded to account for varying modem performance. Note that this is a 101 // timeout, not an enforced wait time, so in most cases, a callback will be received prior to 102 // the wait time elapsing. 103 private static final long WAIT_TIME_MILLIS = 15000L; 104 105 private Context mContext; 106 private ContentResolver mContentResolver; 107 108 private final Executor mSimpleExecutor = Runnable::run; 109 110 private TelephonyManager mTelephonyManager; 111 private PreciseDataConnectionState mPreciseDataConnectionState; 112 113 /** 114 * The original APNs that belongs to the existing data connection. Required to re-insert it 115 * during teardown. 116 */ 117 private List<ApnSetting> mExistingApns; 118 119 /** Selection args for the carrier ID APN. Required to delete the test APN during teardown. */ 120 private String[] mInsertedApnSelectionArgs; 121 122 @Before setUp()123 public void setUp() throws Exception { 124 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 125 assumeTrue( 126 mContext.getPackageManager() 127 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_DATA)); 128 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 129 130 if (mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY 131 || mTelephonyManager.getSubscriptionId() 132 == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 133 fail("This test requires a SIM card with an active subscription/data connection."); 134 } 135 136 InstrumentationRegistry.getInstrumentation() 137 .getUiAutomation() 138 .adoptShellPermissionIdentity(); 139 PreciseDataConnectionStateListener preciseDataConnectionStateCallback = 140 new PreciseDataConnectionStateListener( 141 mTelephonyManager, /* desiredDataState= */ TelephonyManager.DATA_CONNECTED); 142 if (!preciseDataConnectionStateCallback.awaitDataStateChanged(WAIT_TIME_MILLIS)) { 143 fail("Timed out waiting for active data connection."); 144 } 145 146 // The initial data state should be DATA_CONNECTED. 147 if (mPreciseDataConnectionState == null 148 || mPreciseDataConnectionState.getState() != TelephonyManager.DATA_CONNECTED) { 149 fail("This test requires an active data connection."); 150 } 151 152 mContentResolver = mContext.getContentResolver(); 153 } 154 155 @After tearDown()156 public void tearDown() { 157 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_DATA)) { 158 return; 159 } 160 161 if (mInsertedApnSelectionArgs != null) { 162 int deleted = 163 mContentResolver.delete( 164 CARRIER_TABLE_URI, 165 APN_SELECTION_STRING_WITH_CARRIER_ID, 166 mInsertedApnSelectionArgs); 167 } 168 if (mExistingApns != null) { 169 PreciseDataConnectionStateListener pdcsCallback = 170 new PreciseDataConnectionStateListener( 171 mTelephonyManager, 172 /* desiredDataState= */ TelephonyManager.DATA_CONNECTED); 173 // We want to restore the original APNs. Before attempting to re-insert it, we should 174 // first try updating it in case it's still present with an `EDITED_STATUS` of 175 // `USER_DELETED`. If the update does not succeed because the APN doesn't exist, we can 176 // opt to insert it instead. Note that we use stricter selection arguments for update 177 // to ensure we don't accidentally update a different APN. 178 for (ApnSetting existingApn : mExistingApns) { 179 String selectionString = generateSelectionStringForUpdate(existingApn); 180 ContentValues edited = new ContentValues(); 181 // We'll just reset the `EDITED` status of the APN. 182 edited.put(Carriers.EDITED_STATUS, existingApn.getEditedStatus()); 183 int updatedRows = 184 mContentResolver.update( 185 CARRIER_TABLE_URI, 186 edited, 187 selectionString, 188 /*selectionArgs=*/ null); 189 // If no row was updated, re-insert the APN instead. 190 if (updatedRows == 0) { 191 mContentResolver.insert(CARRIER_TABLE_URI, existingApn.toContentValues()); 192 } 193 try { 194 pdcsCallback.awaitDataStateChanged(WAIT_TIME_MILLIS); 195 } catch (InterruptedException e) { 196 // do nothing - we just want to ensure the teardown is complete. 197 } 198 } 199 } 200 201 InstrumentationRegistry.getInstrumentation() 202 .getUiAutomation() 203 .dropShellPermissionIdentity(); 204 } 205 206 /** 207 * Ensures that APNs that consist of a carrier ID column and no other identifying columns such 208 * as MCCMNC/numeric can establish a data connection. 209 */ 210 @Test 211 @AppModeNonSdkSandbox(reason = "SDK sandboxes do not have access to telephony provider") validateDataConnectionWithCarrierIdApn()212 public void validateDataConnectionWithCarrierIdApn() throws Exception { 213 ApnSetting currentApn = mPreciseDataConnectionState.getApnSetting(); 214 validateAndSetupInitialState(currentApn); 215 int carrierId = mTelephonyManager.getSimSpecificCarrierId(); 216 ContentValues apnWithCarrierId = getApnWithCarrierId(currentApn, carrierId); 217 218 // Insert the carrier ID APN. 219 mPreciseDataConnectionState = null; 220 PreciseDataConnectionStateListener pdcsCallback = 221 new PreciseDataConnectionStateListener( 222 mTelephonyManager, /* desiredDataState= */ TelephonyManager.DATA_CONNECTED); 223 int rowsInserted = 224 mContentResolver.bulkInsert( 225 CARRIER_TABLE_URI, new ContentValues[] {apnWithCarrierId}); 226 assertThat(rowsInserted).isEqualTo(1); 227 if (!pdcsCallback.awaitDataStateChanged(WAIT_TIME_MILLIS)) { 228 fail("Timed out waiting for data connected"); 229 } 230 // Generate selection arguments for the APN and store it so we can delete it in cleanup. 231 mInsertedApnSelectionArgs = generateSelectionArgs(currentApn, String.valueOf(carrierId)); 232 233 // Ensure our APN value wasn't somehow overridden (such as in the event a carrier app 234 // exists). 235 assertThat(mPreciseDataConnectionState.getApnSetting().getCarrierId()).isEqualTo(carrierId); 236 assertThat(mPreciseDataConnectionState.getState()) 237 .isEqualTo(TelephonyManager.DATA_CONNECTED); 238 } 239 240 /** 241 * Performs initial setup and validation for the test. 242 * 243 * <p>This skips the test if the existing APN already uses carrier ID. Otherwise, it deletes the 244 * existing APN and ensures data is disconnected. 245 */ validateAndSetupInitialState(ApnSetting currentApn)246 private void validateAndSetupInitialState(ApnSetting currentApn) throws Exception { 247 // Skip the test if the APN already uses carrier ID and data is connected. 248 assumeFalse( 249 "Skipping the test as the APN on the current SIM already uses carrier ID and has a" 250 + " data connection.", 251 apnAlreadyUsesCarrierId(currentApn)); 252 253 mPreciseDataConnectionState = null; 254 PreciseDataConnectionStateListener pdcsCallback = 255 new PreciseDataConnectionStateListener( 256 mTelephonyManager, 257 /* desiredDataState= */ TelephonyManager.DATA_DISCONNECTED); 258 String[] selectionArgs = generateSelectionArgs(currentApn, currentApn.getOperatorNumeric()); 259 Cursor cursor = mContentResolver.query( 260 CARRIER_TABLE_URI, 261 /*projection=*/ null, 262 APN_SELECTION_STRING_WITH_NUMERIC, 263 selectionArgs, 264 /*sortOrder=*/ null); 265 // Store the APNs so we can re-insert them once the test is complete. 266 // Note that multiple APNs can match the selection args, so we'll end up 267 // deleting them too. 268 int count = 0; 269 mExistingApns = new ArrayList<>(); 270 if (cursor != null && cursor.moveToFirst()) { 271 do { 272 ApnSetting apn = ApnSetting.makeApnSetting(cursor); 273 mExistingApns.add(apn); 274 count++; 275 } while (cursor.moveToNext()); 276 } 277 278 int deletedRowCount = 279 mContentResolver.delete( 280 CARRIER_TABLE_URI, 281 APN_SELECTION_STRING_WITH_NUMERIC, 282 selectionArgs); 283 // Ensure we deleted all APNs that match the identifying fields. There might be 284 // multiple APNs with the same identifying fields (but with MVNO match data 285 // and MVNO type). 286 assertThat(deletedRowCount).isEqualTo(count); 287 288 if (!pdcsCallback.awaitDataStateChanged(WAIT_TIME_MILLIS)) { 289 fail("Timed out waiting for data disconnected"); 290 } 291 292 // Data should disconnect without any identifying fields in the default APN. 293 assertThat(mPreciseDataConnectionState.getState()) 294 .isEqualTo(TelephonyManager.DATA_DISCONNECTED); 295 } 296 apnAlreadyUsesCarrierId(ApnSetting apnSetting)297 private boolean apnAlreadyUsesCarrierId(ApnSetting apnSetting) { 298 return apnSetting.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID 299 && TextUtils.isEmpty(apnSetting.getOperatorNumeric()); 300 } 301 302 /** 303 * Replaces the existing APNs identifying fields with carrier ID and returns it as a 304 * ContentValues object. 305 */ getApnWithCarrierId(ApnSetting apnSetting, int carrierId)306 private ContentValues getApnWithCarrierId(ApnSetting apnSetting, int carrierId) { 307 ContentValues apnWithCarrierId = ApnSetting.makeApnSetting(apnSetting).toContentValues(); 308 // Remove non carrier ID identifying fields and insert the carrier ID. 309 List<String> identifyingColumnsToDelete = 310 List.of( 311 Carriers.NUMERIC, 312 Carriers.MCC, 313 Carriers.MNC, 314 Carriers.MVNO_TYPE, 315 Carriers.MVNO_MATCH_DATA); 316 for (String identifyingColumn : identifyingColumnsToDelete) { 317 apnWithCarrierId.remove(identifyingColumn); 318 } 319 apnWithCarrierId.put(Carriers.CARRIER_ID, carrierId); 320 return apnWithCarrierId; 321 } 322 323 /** Generates a selection string for matching the given coluns in a database. */ generateSelectionString(List<String> columns)324 private static String generateSelectionString(List<String> columns) { 325 return String.join("=? AND ", columns) + "=?"; 326 } 327 generateSelectionStringForUpdate(ApnSetting apnSetting)328 private static String generateSelectionStringForUpdate(ApnSetting apnSetting) { 329 ContentValues apnContentValues = apnSetting.toContentValues(); 330 String selectionString = "true"; 331 for (Map.Entry<String, Object> entry : apnContentValues.valueSet()) { 332 // Skip the TYPE column and empty values. This is because ApnSetting rebuilds 333 // the TYPE list based on a bitmask which won't necessarily match the 334 // ordering of the string in the APN database. 335 if (entry.getKey().equals(Carriers.TYPE) 336 || entry.getValue().toString().isEmpty()) { 337 continue; 338 } 339 String value = entry.getValue().toString(); 340 // If the value is a boolean, we should not wrap it in quotes because 341 // SQLite will process the value as TEXT rather than convert it into an INT. 342 if (!entry.getValue().getClass().equals(Boolean.class)) { 343 value = "\"" + value + "\""; 344 } 345 selectionString += " AND " + entry.getKey() + "=" + value; 346 } 347 return selectionString; 348 } 349 350 /** 351 * Generates selection arguments for an APN. 352 * 353 * <p>The selection arguments are based on {@link #BASE_APN_SELECTION_COLUMNS} with the final 354 * argument being either the carrier ID or the numeric to match {@link 355 * #APN_SELECTION_STRING_WITH_NUMERIC} or {@link #APN_SELECTION_STRING_WITH_CARRIER_ID}. 356 */ generateSelectionArgs(ApnSetting baseApn, String numericOrCarrierId)357 private String[] generateSelectionArgs(ApnSetting baseApn, String numericOrCarrierId) { 358 return new String[] { 359 baseApn.getEntryName(), 360 baseApn.getApnName(), 361 ApnSetting.getProtocolStringFromInt(baseApn.getProtocol()), 362 ApnSetting.getProtocolStringFromInt(baseApn.getRoamingProtocol()), 363 Integer.toString(baseApn.getNetworkTypeBitmask()), 364 numericOrCarrierId, 365 }; 366 } 367 368 /** 369 * A oneshot PreciseDataConnectionState listener that listens for a desired data state change on 370 * a cellular network. 371 * 372 * <p>The listener will register itself once instantiated and will unregister itself after 373 * calling {@link PreciseDataConnectionStateListener#awaitDataStateChanged} 374 */ 375 private class PreciseDataConnectionStateListener extends TelephonyCallback 376 implements TelephonyCallback.PreciseDataConnectionStateListener { 377 private final int mDesiredDataState; 378 379 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 380 private final Object mLock = new Object(); 381 /** 382 * Instantiates and registers a PreciseDataConnectionStateListener instance. 383 * 384 * @param telephonyManager the TelephonyManager instance to register the callback on 385 * @param desiredDataState the data state that is expected after performing an action. A 386 * callback will only be fired for this state. See {@link 387 * #onPreciseDataConnectionStateChanged(PreciseDataConnectionState)} for additional 388 * information. 389 */ PreciseDataConnectionStateListener( TelephonyManager telephonyManager, int desiredDataState)390 PreciseDataConnectionStateListener( 391 TelephonyManager telephonyManager, int desiredDataState) { 392 mDesiredDataState = desiredDataState; 393 mPreciseDataConnectionState = null; 394 telephonyManager.registerTelephonyCallback(mSimpleExecutor, this); 395 } 396 awaitDataStateChanged(long timeoutMillis)397 boolean awaitDataStateChanged(long timeoutMillis) throws InterruptedException { 398 try { 399 return mCountDownLatch.await(timeoutMillis, MILLISECONDS); 400 } finally { 401 mTelephonyManager.unregisterTelephonyCallback(this); 402 } 403 } 404 405 @Override onPreciseDataConnectionStateChanged(PreciseDataConnectionState state)406 public void onPreciseDataConnectionStateChanged(PreciseDataConnectionState state) { 407 synchronized (mLock) { 408 ApnSetting apnSetting = state.getApnSetting(); 409 int dataState = state.getState(); 410 // We should only notify if the following conditions are satisfied: 411 // 1. The PDCS belongs to a cellular network. 412 // 2. The APN attached to the PDCS is an internet APN. 413 // 3. The state is the desired data state. 414 boolean isInternetApn = 415 (state.getApnSetting().getApnTypeBitmask() & ApnSetting.TYPE_DEFAULT) 416 == ApnSetting.TYPE_DEFAULT; 417 if (isInternetApn 418 && state.getTransportType() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN 419 && dataState == mDesiredDataState) { 420 mPreciseDataConnectionState = state; 421 mCountDownLatch.countDown(); 422 } 423 } 424 } 425 } 426 } 427