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