• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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