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.net.Uri; 32 import android.platform.test.flag.junit.CheckFlagsRule; 33 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 34 import android.provider.Telephony.Carriers; 35 import android.telephony.AccessNetworkConstants; 36 import android.telephony.PreciseDataConnectionState; 37 import android.telephony.SubscriptionManager; 38 import android.telephony.TelephonyCallback; 39 import android.telephony.TelephonyManager; 40 import android.telephony.data.ApnSetting; 41 import android.text.TextUtils; 42 43 import androidx.test.InstrumentationRegistry; 44 import androidx.test.runner.AndroidJUnit4; 45 46 import com.android.compatibility.common.util.ApiTest; 47 import com.android.internal.telephony.flags.Flags; 48 49 import org.junit.After; 50 import org.junit.Before; 51 import org.junit.Rule; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 55 import java.util.List; 56 import java.util.concurrent.CountDownLatch; 57 import java.util.concurrent.Executor; 58 59 /** 60 * Ensures that APNs that use carrier ID instead of legacy identifiers such as MCCMNC, MVNO type and 61 * match data are able to establish a data connection. 62 */ 63 @ApiTest( 64 apis = { 65 "android.provider.Telephony.Carriers#CONTENT_URI", 66 "android.provider.Telephony.Carriers#CARRIER_ID" 67 }) 68 @RunWith(AndroidJUnit4.class) 69 public class ApnCarrierIdTest { 70 @Rule 71 public final CheckFlagsRule mCheckFlagsRule = 72 DeviceFlagsValueProvider.createCheckFlagsRule(); 73 74 private static final Uri CARRIER_TABLE_URI = Carriers.CONTENT_URI; 75 /** 76 * A base selection string of columns we use to query an APN in the APN database. This excludes 77 * the numeric/carrier ID. 78 * 79 * <p>While it would be ideal to include the Carrier.TYPE here, the ordering of APN types 80 * generated from ApnSetting may not match the ordering when we query the type from the APN 81 * database, which makes it more non-trivial to query using type. 82 */ 83 private static final String BASE_APN_SELECTION_COLUMNS = 84 generateSelectionString( 85 List.of( 86 Carriers.NAME, 87 Carriers.APN, 88 Carriers.PROTOCOL, 89 Carriers.ROAMING_PROTOCOL, 90 Carriers.NETWORK_TYPE_BITMASK)); 91 92 private static final String APN_SELECTION_STRING_WITH_NUMERIC = 93 BASE_APN_SELECTION_COLUMNS + "AND " + Carriers.NUMERIC + "=?"; 94 private static final String APN_SELECTION_STRING_WITH_CARRIER_ID = 95 BASE_APN_SELECTION_COLUMNS + "AND " + Carriers.CARRIER_ID + "=?"; 96 97 // The wait time is padded to account for varying modem performance. Note that this is a 98 // timeout, not an enforced wait time, so in most cases, a callback will be received prior to 99 // the wait time elapsing. 100 private static final long WAIT_TIME_MILLIS = 10000L; 101 102 private Context mContext; 103 private ContentResolver mContentResolver; 104 105 private final Executor mSimpleExecutor = Runnable::run; 106 107 private TelephonyManager mTelephonyManager; 108 private PreciseDataConnectionState mPreciseDataConnectionState; 109 110 /** 111 * The original APN that belongs to the existing data connection. Required to re-insert it 112 * during teardown. 113 */ 114 private ContentValues mExistingApn; 115 /** Selection args for the carrier ID APN. Required to delete the test APN during teardown. */ 116 private String[] mInsertedApnSelectionArgs; 117 118 @Before setUp()119 public void setUp() throws Exception { 120 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 121 if (Flags.enforceTelephonyFeatureMappingForPublicApis()) { 122 assumeTrue(mContext.getPackageManager().hasSystemFeature( 123 PackageManager.FEATURE_TELEPHONY_DATA)); 124 } else { 125 assumeTrue(mContext.getPackageManager().hasSystemFeature( 126 PackageManager.FEATURE_TELEPHONY)); 127 } 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 preciseDataConnectionStateCallback.awaitDataStateChanged(WAIT_TIME_MILLIS); 143 144 // The initial data state should be DATA_CONNECTED. 145 if (mPreciseDataConnectionState == null 146 || mPreciseDataConnectionState.getState() != TelephonyManager.DATA_CONNECTED) { 147 fail("This test requires an active data connection."); 148 } 149 150 mContentResolver = mContext.getContentResolver(); 151 } 152 153 @After tearDown()154 public void tearDown() { 155 if (Flags.enforceTelephonyFeatureMappingForPublicApis()) { 156 if (!mContext.getPackageManager().hasSystemFeature( 157 PackageManager.FEATURE_TELEPHONY_DATA)) { 158 return; 159 } 160 } else { 161 if (!mContext.getPackageManager().hasSystemFeature( 162 PackageManager.FEATURE_TELEPHONY)) { 163 return; 164 } 165 } 166 167 if (mInsertedApnSelectionArgs != null) { 168 int deleted = 169 mContentResolver.delete( 170 CARRIER_TABLE_URI, 171 APN_SELECTION_STRING_WITH_CARRIER_ID, 172 mInsertedApnSelectionArgs); 173 } 174 if (mExistingApn != null) { 175 PreciseDataConnectionStateListener pdcsCallback = 176 new PreciseDataConnectionStateListener( 177 mTelephonyManager, 178 /* desiredDataState= */ TelephonyManager.DATA_CONNECTED); 179 mContentResolver.insert(CARRIER_TABLE_URI, mExistingApn); 180 try { 181 pdcsCallback.awaitDataStateChanged(WAIT_TIME_MILLIS); 182 } catch (InterruptedException e) { 183 // do nothing - we just want to ensure the teardown is complete. 184 } 185 } 186 187 InstrumentationRegistry.getInstrumentation() 188 .getUiAutomation() 189 .dropShellPermissionIdentity(); 190 } 191 192 /** 193 * Ensures that APNs that consist of a carrier ID column and no other identifying columns such 194 * as MCCMNC/numeric can establish a data connection. 195 */ 196 @Test validateDataConnectionWithCarrierIdApn()197 public void validateDataConnectionWithCarrierIdApn() throws Exception { 198 ApnSetting currentApn = mPreciseDataConnectionState.getApnSetting(); 199 validateAndSetupInitialState(currentApn); 200 int carrierId = mTelephonyManager.getSimSpecificCarrierId(); 201 ContentValues apnWithCarrierId = getApnWithCarrierId(currentApn, carrierId); 202 203 // Insert the carrier ID APN. 204 mPreciseDataConnectionState = null; 205 PreciseDataConnectionStateListener pdcsCallback = 206 new PreciseDataConnectionStateListener( 207 mTelephonyManager, /* desiredDataState= */ TelephonyManager.DATA_CONNECTED); 208 int rowsInserted = 209 mContentResolver.bulkInsert( 210 CARRIER_TABLE_URI, new ContentValues[] {apnWithCarrierId}); 211 assertThat(rowsInserted).isEqualTo(1); 212 pdcsCallback.awaitDataStateChanged(WAIT_TIME_MILLIS); 213 // Generate selection arguments for the APN and store it so we can delete it in cleanup. 214 mInsertedApnSelectionArgs = generateSelectionArgs(currentApn, String.valueOf(carrierId)); 215 216 // Ensure our APN value wasn't somehow overridden (such as in the event a carrier app 217 // exists). 218 assertThat(mPreciseDataConnectionState.getApnSetting().getCarrierId()).isEqualTo(carrierId); 219 assertThat(mPreciseDataConnectionState.getState()) 220 .isEqualTo(TelephonyManager.DATA_CONNECTED); 221 } 222 223 /** 224 * Performs initial setup and validation for the test. 225 * 226 * <p>This skips the test if the existing APN already uses carrier ID. Otherwise, it deletes the 227 * existing APN and ensures data is disconnected. 228 */ validateAndSetupInitialState(ApnSetting currentApn)229 private void validateAndSetupInitialState(ApnSetting currentApn) throws Exception { 230 // Skip the test if the APN already uses carrier ID and data is connected. 231 assumeFalse( 232 "Skipping the test as the APN on the current SIM already uses carrier ID and has a" 233 + " data connection.", 234 apnAlreadyUsesCarrierId(currentApn)); 235 236 mPreciseDataConnectionState = null; 237 PreciseDataConnectionStateListener pdcsCallback = 238 new PreciseDataConnectionStateListener( 239 mTelephonyManager, 240 /* desiredDataState= */ TelephonyManager.DATA_DISCONNECTED); 241 int deletedRowCount = 242 mContentResolver.delete( 243 CARRIER_TABLE_URI, 244 APN_SELECTION_STRING_WITH_NUMERIC, 245 generateSelectionArgs(currentApn, currentApn.getOperatorNumeric())); 246 assertThat(deletedRowCount).isEqualTo(1); 247 // Store the APN so we can re-insert it once the test is complete. 248 mExistingApn = currentApn.toContentValues(); 249 pdcsCallback.awaitDataStateChanged(WAIT_TIME_MILLIS); 250 251 // Data should disconnect without any identifying fields in the default APN. 252 assertThat(mPreciseDataConnectionState.getState()) 253 .isEqualTo(TelephonyManager.DATA_DISCONNECTED); 254 } 255 apnAlreadyUsesCarrierId(ApnSetting apnSetting)256 private boolean apnAlreadyUsesCarrierId(ApnSetting apnSetting) { 257 return apnSetting.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID 258 && TextUtils.isEmpty(apnSetting.getOperatorNumeric()); 259 } 260 261 /** 262 * Replaces the existing APNs identifying fields with carrier ID and returns it as a 263 * ContentValues object. 264 */ getApnWithCarrierId(ApnSetting apnSetting, int carrierId)265 private ContentValues getApnWithCarrierId(ApnSetting apnSetting, int carrierId) { 266 ContentValues apnWithCarrierId = ApnSetting.makeApnSetting(apnSetting).toContentValues(); 267 // Remove non carrier ID identifying fields and insert the carrier ID. 268 List<String> identifyingColumnsToDelete = 269 List.of( 270 Carriers.NUMERIC, 271 Carriers.MCC, 272 Carriers.MNC, 273 Carriers.MVNO_TYPE, 274 Carriers.MVNO_MATCH_DATA); 275 for (String identifyingColumn : identifyingColumnsToDelete) { 276 apnWithCarrierId.remove(identifyingColumn); 277 } 278 apnWithCarrierId.put(Carriers.CARRIER_ID, carrierId); 279 return apnWithCarrierId; 280 } 281 282 /** Generates a selection string for matching the given coluns in a database. */ generateSelectionString(List<String> columns)283 private static String generateSelectionString(List<String> columns) { 284 return String.join("=? AND ", columns) + "=?"; 285 } 286 287 /** 288 * Generates selection arguments for an APN. 289 * 290 * <p>The selection arguments are based on {@link #BASE_APN_SELECTION_COLUMNS} with the final 291 * argument being either the carrier ID or the numeric to match {@link 292 * #APN_SELECTION_STRING_WITH_NUMERIC} or {@link #APN_SELECTION_STRING_WITH_CARRIER_ID}. 293 */ generateSelectionArgs(ApnSetting baseApn, String numericOrCarrierId)294 private String[] generateSelectionArgs(ApnSetting baseApn, String numericOrCarrierId) { 295 return new String[] { 296 baseApn.getEntryName(), 297 baseApn.getApnName(), 298 ApnSetting.getProtocolStringFromInt(baseApn.getProtocol()), 299 ApnSetting.getProtocolStringFromInt(baseApn.getRoamingProtocol()), 300 Integer.toString(baseApn.getNetworkTypeBitmask()), 301 numericOrCarrierId, 302 }; 303 } 304 305 /** 306 * A oneshot PreciseDataConnectionState listener that listens for a desired data state change on 307 * a cellular network. 308 * 309 * <p>The listener will register itself once instantiated and will unregister itself after 310 * calling {@link PreciseDataConnectionStateListener#awaitDataStateChanged} 311 */ 312 private class PreciseDataConnectionStateListener extends TelephonyCallback 313 implements TelephonyCallback.PreciseDataConnectionStateListener { 314 private final int mDesiredDataState; 315 316 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 317 private final Object mLock = new Object(); 318 /** 319 * Instantiates and registers a PreciseDataConnectionStateListener instance. 320 * 321 * @param telephonyManager the TelephonyManager instance to register the callback on 322 * @param desiredDataState the data state that is expected after performing an action. A 323 * callback will only be fired for this state. See {@link 324 * #onPreciseDataConnectionStateChanged(PreciseDataConnectionState)} for additional 325 * information. 326 */ PreciseDataConnectionStateListener( TelephonyManager telephonyManager, int desiredDataState)327 PreciseDataConnectionStateListener( 328 TelephonyManager telephonyManager, int desiredDataState) { 329 mDesiredDataState = desiredDataState; 330 mPreciseDataConnectionState = null; 331 telephonyManager.registerTelephonyCallback(mSimpleExecutor, this); 332 } 333 awaitDataStateChanged(long timeoutMillis)334 void awaitDataStateChanged(long timeoutMillis) throws InterruptedException { 335 try { 336 mCountDownLatch.await(timeoutMillis, MILLISECONDS); 337 } finally { 338 mTelephonyManager.unregisterTelephonyCallback(this); 339 } 340 } 341 342 @Override onPreciseDataConnectionStateChanged(PreciseDataConnectionState state)343 public void onPreciseDataConnectionStateChanged(PreciseDataConnectionState state) { 344 synchronized (mLock) { 345 ApnSetting apnSetting = state.getApnSetting(); 346 int dataState = state.getState(); 347 // We should only notify if the following conditions are satisfied: 348 // 1. The PDCS belongs to a cellular network. 349 // 2. The APN attached to the PDCS is an internet APN. 350 // 3. The state is the desired data state. 351 boolean isInternetApn = 352 (state.getApnSetting().getApnTypeBitmask() & ApnSetting.TYPE_DEFAULT) 353 == ApnSetting.TYPE_DEFAULT; 354 if (isInternetApn 355 && state.getTransportType() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN 356 && dataState == mDesiredDataState) { 357 mPreciseDataConnectionState = state; 358 mCountDownLatch.countDown(); 359 } 360 } 361 } 362 } 363 } 364