blob: e4fbee6805d39e63030a186fb00c8b0f6b697cf6 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.carrierapi.cts;
import static android.carrierapi.cts.FcpTemplate.FILE_IDENTIFIER;
import static android.carrierapi.cts.IccUtils.bytesToHexString;
import static android.carrierapi.cts.IccUtils.hexStringToBytes;
import static android.telephony.IccOpenLogicalChannelResponse.INVALID_CHANNEL;
import static android.telephony.IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER;
import static android.telephony.TelephonyManager.DATA_ENABLED_REASON_THERMAL;
import static android.telephony.TelephonyManager.DATA_ENABLED_REASON_USER;
import static com.android.compatibility.common.util.UiccUtil.UiccCertificate.CTS_UICC_2021;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.platform.test.annotations.SystemUserOnly;
import android.provider.Telephony;
import android.provider.VoicemailContract;
import android.telephony.AccessNetworkConstants;
import android.telephony.AvailableNetworkInfo;
import android.telephony.CarrierConfigManager;
import android.telephony.IccOpenLogicalChannelResponse;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrengthUpdateRequest;
import android.telephony.SignalThresholdInfo;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.NetworkSlicingConfig;
import android.util.Base64;
import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.UiccUtil;
import com.google.common.collect.Range;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
/**
* Unit tests for various carrier-related APIs.
*
* <p>Test using `atest CtsCarrierApiTestCases:CarrierApiTest` or `make cts -j64 && cts-tradefed run
* cts -m CtsCarrierApiTestCases --test android.carrierapi.cts.CarrierApiTest`
*/
// TODO(b/130187425): Split CarrierApiTest apart to have separate test classes for functionality
@RunWith(AndroidJUnit4.class)
public class CarrierApiTest extends BaseCarrierApiTest {
private static final String TAG = "CarrierApiTest";
private TelephonyManager mTelephonyManager;
private CarrierConfigManager mCarrierConfigManager;
private SubscriptionManager mSubscriptionManager;
private ContentProviderClient mVoicemailProvider;
private ContentProviderClient mStatusProvider;
private Uri mVoicemailContentUri;
private Uri mStatusContentUri;
private String selfPackageName;
private HandlerThread mListenerThread;
// The minimum allocatable logical channel number, per TS 102 221 Section 11.1.17.1
private static final int MIN_LOGICAL_CHANNEL = 1;
// The maximum allocatable logical channel number in the standard range, per TS 102 221 Section
// 11.1.17.1
private static final int MAX_LOGICAL_CHANNEL = 3;
// Class bytes. The logical channel used should be included for bits b2b1. TS 102 221 Table 11.5
private static final int CLA_GET_RESPONSE = 0x00;
private static final int CLA_MANAGE_CHANNEL = 0x00;
private static final int CLA_READ_BINARY = 0x00;
private static final int CLA_SELECT = 0x00;
private static final int CLA_STATUS = 0x80;
private static final String CLA_STATUS_STRING = "80";
// APDU Instruction Bytes. TS 102 221 Section 10.1.2
private static final int COMMAND_GET_RESPONSE = 0xC0;
private static final int COMMAND_MANAGE_CHANNEL = 0x70;
private static final int COMMAND_READ_BINARY = 0xB0;
private static final int COMMAND_SELECT = 0xA4;
private static final int COMMAND_STATUS = 0xF2;
private static final String COMMAND_STATUS_STRING = "F2";
// Status words. TS 102 221 Section 10.2.1
private static final byte[] STATUS_NORMAL = {(byte) 0x90, (byte) 0x00};
private static final String STATUS_NORMAL_STRING = "9000";
private static final String STATUS_BYTES_REMAINING = "61";
private static final String STATUS_WARNING_A = "62";
private static final String STATUS_WARNING_B = "63";
private static final String STATUS_FILE_NOT_FOUND = "6a82";
private static final String STATUS_INCORRECT_PARAMETERS = "6a86";
private static final String STATUS_WRONG_PARAMETERS = "6b00";
private static final Set<String> INVALID_PARAMETERS_STATUSES =
new HashSet<>(Arrays.asList(STATUS_INCORRECT_PARAMETERS, STATUS_WRONG_PARAMETERS));
private static final String STATUS_WRONG_CLASS = "6e00";
// File ID for the EF ICCID. TS 102 221
private static final String ICCID_FILE_ID = "2FE2";
// File ID for the master file. TS 102 221
private static final String MF_FILE_ID = "3F00";
private static final int MF_FILE_ID_HEX = 0x3F00;
// File ID for the MF Access Rule Reference. TS 102 221
private static final String MF_ARR_FILE_ID = "2F06";
private static final String ALPHA_TAG_A = "tagA";
private static final String ALPHA_TAG_B = "tagB";
private static final String NUMBER_A = "1234567890";
private static final String NUMBER_B = "0987654321";
private static final String TESTING_PLMN = "12345";
private static final String EAP_SIM_AKA_RAND = "11111111111111111111111111111111";
// Derived from TS 134 108#8.1.2. Based on EAP_SIM_AKA_RAND and assumed K value of
// 000102030405060708090A0B0C0D0E0F, per TS 134 108#8.2
private static final String EAP_AKA_AUTN = "12351417161900001130131215141716";
// EAP-AKA Response Format: [DB][Length][RES][Length][CK][Length][IK]
private static final int EAP_AKA_RESPONSE_LENGTH = 1 + 1 + 16 + 1 + 16 + 1 + 16;
// Derived from TS 134 108#8.1.2. Based on EAP_SIM_AKA_RAND and assumed K value of
// 000102030405060708090A0B0C0D0E0F, per TS 134 108#8.2.
// Format: [DB][Length][RES][Length][CK][Length][IK]
private static final String EXPECTED_EAP_AKA_RESULT =
"DB10111013121514171619181B1A1D1C1F1E"
+ "101013121514171619181B1A1D1C1F1E11"
+ "1013121514171619181B1A1D1C1F1E1110";
// Derived from TS 134 108#8.1.2 and TS 133 102#6.8.1.2. Based on EAP_SIM_AKA_RAND and assumed K
// value of 000102030405060708090A0B0C0D0E0F, per TS 134 108#8.2.
// Format: [Length][SRES][Length][Kc]
private static final String EXPECTED_EAP_SIM_RESULT = "0400000000080000000000000000";
private static final int DSDS_PHONE_COUNT = 2;
@Before
public void setUp() throws Exception {
Context context = getContext();
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
selfPackageName = context.getPackageName();
mVoicemailContentUri = VoicemailContract.Voicemails.buildSourceUri(selfPackageName);
mVoicemailProvider =
context.getContentResolver().acquireContentProviderClient(mVoicemailContentUri);
mStatusContentUri = VoicemailContract.Status.buildSourceUri(selfPackageName);
mStatusProvider =
context.getContentResolver().acquireContentProviderClient(mStatusContentUri);
mListenerThread = new HandlerThread("CarrierApiTest");
mListenerThread.start();
}
@After
public void tearDown() throws Exception {
if (!werePreconditionsSatisfied()) return;
mListenerThread.quit();
try {
mStatusProvider.delete(mStatusContentUri, null, null);
mVoicemailProvider.delete(mVoicemailContentUri, null, null);
} catch (Exception e) {
Log.w(TAG, "Failed to clean up voicemail tables in tearDown", e);
}
}
@Test
public void testSimCardPresent() {
assertWithMessage("This test requires a SIM card")
.that(mTelephonyManager.getSimState())
.isNotEqualTo(TelephonyManager.SIM_STATE_ABSENT);
}
@Test
public void testHasCarrierPrivileges() {
assertWithMessage(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE)
.that(mTelephonyManager.hasCarrierPrivileges())
.isTrue();
}
private static void assertUpdateAvailableNetworkSuccess(int value) {
assertThat(value).isEqualTo(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS);
}
private static void assertUpdateAvailableNetworkNoOpportunisticSubAvailable(int value) {
assertThat(value)
.isEqualTo(
TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE);
}
private static void assertSetOpportunisticSubSuccess(int value) {
assertThat(value).isEqualTo(TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS);
}
private int getFirstActivateCarrierPrivilegedSubscriptionId() {
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
List<SubscriptionInfo> subscriptionInfos =
mSubscriptionManager.getActiveSubscriptionInfoList();
if (subscriptionInfos != null) {
for (SubscriptionInfo info : subscriptionInfos) {
TelephonyManager telephonyManager =
mTelephonyManager.createForSubscriptionId(info.getSubscriptionId());
if (telephonyManager.hasCarrierPrivileges()) {
subId = info.getSubscriptionId();
return subId;
}
}
}
return subId;
}
@Test
public void testUpdateAvailableNetworksWithCarrierPrivilege() {
int subIdWithCarrierPrivilege = getFirstActivateCarrierPrivilegedSubscriptionId();
int activeSubscriptionInfoCount =
ShellIdentityUtils.invokeMethodWithShellPermissions(
mSubscriptionManager, (tm) -> tm.getActiveSubscriptionInfoCount());
if (mTelephonyManager.getPhoneCount() == 1) {
return;
}
/* TODO: b/145993690 */
if (mTelephonyManager.getPhoneCount() == 2 && activeSubscriptionInfoCount != 2) {
/* This test requires two SIM cards */
return;
}
if (subIdWithCarrierPrivilege == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
/* This test requires SIM with carrier privilege */
return;
}
List<SubscriptionInfo> subscriptionInfoList =
mSubscriptionManager.getOpportunisticSubscriptions();
List<String> mccMncs = new ArrayList<String>();
List<Integer> bands = new ArrayList<Integer>();
List<AvailableNetworkInfo> availableNetworkInfos = new ArrayList<AvailableNetworkInfo>();
Consumer<Integer> callbackSuccess = CarrierApiTest::assertUpdateAvailableNetworkSuccess;
Consumer<Integer> callbackNoOpportunisticSubAvailable =
CarrierApiTest::assertUpdateAvailableNetworkNoOpportunisticSubAvailable;
Consumer<Integer> setOpCallbackSuccess = CarrierApiTest::assertSetOpportunisticSubSuccess;
if (subscriptionInfoList == null
|| subscriptionInfoList.size() == 0
|| !mSubscriptionManager.isActiveSubscriptionId(
subscriptionInfoList.get(0).getSubscriptionId())) {
try {
AvailableNetworkInfo availableNetworkInfo =
new AvailableNetworkInfo(
subIdWithCarrierPrivilege,
AvailableNetworkInfo.PRIORITY_HIGH,
mccMncs,
bands);
availableNetworkInfos.add(availableNetworkInfo);
// Call updateAvailableNetworks without opportunistic subscription.
// callbackNoOpportunisticSubAvailable is expected to be triggered
// and the return value will be checked against
// UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE
mTelephonyManager.updateAvailableNetworks(
availableNetworkInfos,
AsyncTask.SERIAL_EXECUTOR,
callbackNoOpportunisticSubAvailable);
} finally {
// clear all the operations at the end of test.
availableNetworkInfos.clear();
mTelephonyManager.updateAvailableNetworks(
availableNetworkInfos,
AsyncTask.SERIAL_EXECUTOR,
callbackNoOpportunisticSubAvailable);
}
} else {
// This is case of DSDS phone, one active opportunistic subscription and one
// active primary subscription.
int resultSubId;
try {
AvailableNetworkInfo availableNetworkInfo =
new AvailableNetworkInfo(
subscriptionInfoList.get(0).getSubscriptionId(),
AvailableNetworkInfo.PRIORITY_HIGH,
mccMncs,
bands);
availableNetworkInfos.add(availableNetworkInfo);
mTelephonyManager.updateAvailableNetworks(
availableNetworkInfos, AsyncTask.SERIAL_EXECUTOR, callbackSuccess);
// wait for the data change to take effect
waitForMs(500);
// Call setPreferredData and reconfirm with getPreferred data
// that the same is updated.
int preferSubId = subscriptionInfoList.get(0).getSubscriptionId();
mTelephonyManager.setPreferredOpportunisticDataSubscription(
preferSubId, false, AsyncTask.SERIAL_EXECUTOR, setOpCallbackSuccess);
// wait for the data change to take effect
waitForMs(500);
resultSubId = mTelephonyManager.getPreferredOpportunisticDataSubscription();
assertThat(resultSubId).isEqualTo(preferSubId);
} finally {
// clear all the operations at the end of test.
availableNetworkInfos.clear();
mTelephonyManager.updateAvailableNetworks(
availableNetworkInfos, AsyncTask.SERIAL_EXECUTOR, callbackSuccess);
waitForMs(500);
mTelephonyManager.setPreferredOpportunisticDataSubscription(
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
false,
AsyncTask.SERIAL_EXECUTOR,
callbackSuccess);
waitForMs(500);
resultSubId = mTelephonyManager.getPreferredOpportunisticDataSubscription();
assertThat(resultSubId).isEqualTo(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
}
}
public static void waitForMs(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Log.d(TAG, "InterruptedException while waiting: " + e);
}
}
@Test
public void testGetIccAuthentication() {
// EAP-SIM rand is 16 bytes.
String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";
String base64Challenge2 = "EMNxjsFrPCpm+KcgCmQGnwQ=";
try {
assertWithMessage("getIccAuthentication should return null for empty data.")
.that(
mTelephonyManager.getIccAuthentication(
TelephonyManager.APPTYPE_USIM,
TelephonyManager.AUTHTYPE_EAP_AKA,
""))
.isNull();
String response =
mTelephonyManager.getIccAuthentication(
TelephonyManager.APPTYPE_USIM,
TelephonyManager.AUTHTYPE_EAP_SIM,
base64Challenge);
assertWithMessage("Response to EAP-SIM Challenge must not be Null.")
.that(response)
.isNotNull();
// response is base64 encoded. After decoding, the value should be:
// 1 length byte + SRES(4 bytes) + 1 length byte + Kc(8 bytes)
byte[] result = android.util.Base64.decode(response, android.util.Base64.DEFAULT);
assertThat(result).hasLength(14);
String response2 =
mTelephonyManager.getIccAuthentication(
TelephonyManager.APPTYPE_USIM,
TelephonyManager.AUTHTYPE_EAP_SIM,
base64Challenge2);
assertWithMessage("Two responses must be different")
.that(response)
.isNotEqualTo(response2);
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
@SystemUserOnly(reason = "b/177921545, broadcast sent only to primary user")
public void testSendDialerSpecialCode() {
IntentReceiver intentReceiver = new IntentReceiver();
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Telephony.Sms.Intents.SECRET_CODE_ACTION);
intentFilter.addDataScheme("android_secret_code");
Context context = getContext();
context.registerReceiver(intentReceiver, intentFilter);
try {
mTelephonyManager.sendDialerSpecialCode("4636");
assertWithMessage(
"Did not receive expected Intent: "
+ Telephony.Sms.Intents.SECRET_CODE_ACTION)
.that(intentReceiver.waitForReceive())
.isTrue();
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
} catch (InterruptedException e) {
Log.d(TAG, "Broadcast receiver wait was interrupted.");
} finally {
context.unregisterReceiver(intentReceiver);
}
}
@Test
public void testSubscriptionInfoListing() {
try {
assertThat(mSubscriptionManager.getActiveSubscriptionInfoCount()).isGreaterThan(0);
List<SubscriptionInfo> subInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList();
assertWithMessage("getActiveSubscriptionInfoList() returned null")
.that(subInfoList)
.isNotNull();
assertWithMessage("getActiveSubscriptionInfoList() returned an empty list")
.that(subInfoList)
.isNotEmpty();
for (SubscriptionInfo info : subInfoList) {
TelephonyManager tm =
mTelephonyManager.createForSubscriptionId(info.getSubscriptionId());
assertWithMessage(
"getActiveSubscriptionInfoList() returned an inaccessible"
+ " subscription")
.that(tm.hasCarrierPrivileges())
.isTrue();
// Check other APIs to make sure they are accessible and return consistent info.
SubscriptionInfo infoForSlot =
mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(
info.getSimSlotIndex());
assertWithMessage("getActiveSubscriptionInfoForSimSlotIndex() returned null")
.that(infoForSlot)
.isNotNull();
assertWithMessage(
"getActiveSubscriptionInfoForSimSlotIndex() returned inconsistent"
+ " info")
.that(infoForSlot.getSubscriptionId())
.isEqualTo(info.getSubscriptionId());
SubscriptionInfo infoForSubId =
mSubscriptionManager.getActiveSubscriptionInfo(info.getSubscriptionId());
assertWithMessage("getActiveSubscriptionInfo() returned null")
.that(infoForSubId)
.isNotNull();
assertWithMessage("getActiveSubscriptionInfo() returned inconsistent info")
.that(infoForSubId.getSubscriptionId())
.isEqualTo(info.getSubscriptionId());
}
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
public void testCarrierConfigIsAccessible() {
try {
PersistableBundle bundle = mCarrierConfigManager.getConfig();
assertWithMessage("CarrierConfigManager#getConfig() returned null")
.that(bundle)
.isNotNull();
assertWithMessage("CarrierConfigManager#getConfig() returned empty bundle")
.that(bundle.isEmpty())
.isFalse();
int subId = SubscriptionManager.getDefaultSubscriptionId();
bundle = mCarrierConfigManager.getConfigForSubId(subId);
assertWithMessage("CarrierConfigManager#getConfigForSubId() returned null")
.that(bundle)
.isNotNull();
assertWithMessage("CarrierConfigManager#getConfigForSubId() returned empty bundle")
.that(bundle.isEmpty())
.isFalse();
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
public void testTelephonyApisAreAccessible() {
// The following methods may return any value depending on the state of the device. Simply
// call them to make sure they do not throw any exceptions. Methods that return a device
// identifier will be accessible to apps with carrier privileges in Q, but this may change
// in a future release.
try {
mTelephonyManager.getDeviceId();
mTelephonyManager.getImei();
mTelephonyManager.getMeid();
mTelephonyManager.getDeviceSoftwareVersion();
mTelephonyManager.getNai();
mTelephonyManager.getDataNetworkType();
mTelephonyManager.getVoiceNetworkType();
mTelephonyManager.getSimSerialNumber();
mTelephonyManager.getSubscriberId();
mTelephonyManager.getGroupIdLevel1();
mTelephonyManager.getLine1Number();
mTelephonyManager.getVoiceMailNumber();
mTelephonyManager.getVisualVoicemailPackageName();
mTelephonyManager.getVoiceMailAlphaTag();
mTelephonyManager.getForbiddenPlmns();
mTelephonyManager.getServiceState();
mTelephonyManager.getManualNetworkSelectionPlmn();
mTelephonyManager.setForbiddenPlmns(new ArrayList<String>());
int activeModemCount = mTelephonyManager.getActiveModemCount();
for (int i = 0; i < activeModemCount; i++) {
mTelephonyManager.isModemEnabledForSlot(i);
}
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
public void testVoicemailTableIsAccessible() throws Exception {
ContentValues value = new ContentValues();
value.put(VoicemailContract.Voicemails.NUMBER, "0123456789");
value.put(VoicemailContract.Voicemails.SOURCE_PACKAGE, selfPackageName);
try {
Uri uri = mVoicemailProvider.insert(mVoicemailContentUri, value);
assertThat(uri).isNotNull();
Cursor cursor =
mVoicemailProvider.query(
uri,
new String[] {
VoicemailContract.Voicemails.NUMBER,
VoicemailContract.Voicemails.SOURCE_PACKAGE
},
null,
null,
null);
assertThat(cursor).isNotNull();
assertThat(cursor.moveToFirst()).isTrue();
assertThat(cursor.getString(0)).isEqualTo("0123456789");
assertThat(cursor.getString(1)).isEqualTo(selfPackageName);
assertThat(cursor.moveToNext()).isFalse();
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
public void testVoicemailStatusTableIsAccessible() throws Exception {
ContentValues value = new ContentValues();
value.put(
VoicemailContract.Status.CONFIGURATION_STATE,
VoicemailContract.Status.CONFIGURATION_STATE_OK);
value.put(VoicemailContract.Status.SOURCE_PACKAGE, selfPackageName);
try {
Uri uri = mStatusProvider.insert(mStatusContentUri, value);
assertThat(uri).isNotNull();
Cursor cursor =
mVoicemailProvider.query(
uri,
new String[] {
VoicemailContract.Status.CONFIGURATION_STATE,
VoicemailContract.Status.SOURCE_PACKAGE
},
null,
null,
null);
assertThat(cursor).isNotNull();
assertThat(cursor.moveToFirst()).isTrue();
assertThat(cursor.getInt(0)).isEqualTo(VoicemailContract.Status.CONFIGURATION_STATE_OK);
assertThat(cursor.getString(1)).isEqualTo(selfPackageName);
assertThat(cursor.moveToNext()).isFalse();
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
static final int READ_PHONE_STATE_LISTENERS =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
| PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
| PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
static final int READ_PRECISE_PHONE_STATE_LISTENERS =
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
| PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
| PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES
| PhoneStateListener.LISTEN_REGISTRATION_FAILURE
| PhoneStateListener.LISTEN_BARRING_INFO;
static final int CARRIER_PRIVILEGE_LISTENERS =
READ_PHONE_STATE_LISTENERS | READ_PRECISE_PHONE_STATE_LISTENERS;
@Test
public void testGetManualNetworkSelectionPlmnPersisted() throws Exception {
if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
try {
mTelephonyManager.setNetworkSelectionModeManual(
TESTING_PLMN /* operatorNumeric */, true /* persistSelection */);
String plmn = mTelephonyManager.getManualNetworkSelectionPlmn();
assertThat(plmn).isEqualTo(TESTING_PLMN);
} finally {
mTelephonyManager.setNetworkSelectionModeAutomatic();
}
}
@Test
public void testPhoneStateListener() throws Exception {
PhoneStateListener psl = new PhoneStateListener((Runnable r) -> {});
try {
mTelephonyManager.listen(psl, CARRIER_PRIVILEGE_LISTENERS);
} finally {
mTelephonyManager.listen(psl, PhoneStateListener.LISTEN_NONE);
}
}
@Test
public void testIsManualNetworkSelectionAllowed() throws Exception {
if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
try {
assertThat(mTelephonyManager.isManualNetworkSelectionAllowed()).isTrue();
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
public void testGetNetworkSelectionMode() throws Exception {
try {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mTelephonyManager, (tm) -> tm.setNetworkSelectionModeAutomatic());
int networkMode = mTelephonyManager.getNetworkSelectionMode();
assertThat(networkMode).isEqualTo(TelephonyManager.NETWORK_SELECTION_MODE_AUTO);
} catch (SecurityException e) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
@Test
public void testSubscriptionInfoChangeListener() throws Exception {
final AtomicReference<SecurityException> error = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
new Handler(mListenerThread.getLooper())
.post(
() -> {
SubscriptionManager.OnSubscriptionsChangedListener listener =
new SubscriptionManager.OnSubscriptionsChangedListener();
try {
mSubscriptionManager.addOnSubscriptionsChangedListener(listener);
} catch (SecurityException e) {
error.set(e);
} finally {
mSubscriptionManager.removeOnSubscriptionsChangedListener(listener);
latch.countDown();
}
});
assertWithMessage("Test timed out").that(latch.await(30L, TimeUnit.SECONDS)).isTrue();
if (error.get() != null) {
fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
}
}
/**
* Test that it's possible to open logical channels to the ICC. This mirrors the Manage Channel
* command described in TS 102 221 Section 11.1.17.
*/
@Test
public void testIccOpenLogicalChannel() {
// The AID here doesn't matter - we just need to open a valid connection. In this case, the
// specified AID ("") opens a channel and selects the MF.
IccOpenLogicalChannelResponse response = mTelephonyManager.iccOpenLogicalChannel("");
final int logicalChannel = response.getChannel();
try {
verifyValidIccOpenLogicalChannelResponse(response);
} finally {
mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
}
}
@Test
public void testIccOpenLogicalChannelWithValidP2() {
// {@link TelephonyManager#iccOpenLogicalChannel} sends a Manage Channel (open) APDU
// followed by a Select APDU with the given AID and p2 values. See Open Mobile API
// Specification v3.2 Section 6.2.7.h and TS 102 221 for details.
int p2 = 0x0C; // '0C' for no data returned (TS 102 221 Section 11.1.1.2)
IccOpenLogicalChannelResponse response = mTelephonyManager.iccOpenLogicalChannel("", p2);
final int logicalChannel = response.getChannel();
try {
verifyValidIccOpenLogicalChannelResponse(response);
} finally {
mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
}
}
@Test
public void testIccOpenLogicalChannelWithInvalidP2() {
// Valid p2 values are defined in TS 102 221 Table 11.2. Per Table 11.2, 0xF0 should be
// invalid. Any p2 values that produce non '9000'/'62xx'/'63xx' status words are treated as
// an error and the channel is not opened. Due to compatibility issues with older devices,
// this check is only enabled for new devices launching on Q+.
if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.Q) {
int p2 = 0xF0;
IccOpenLogicalChannelResponse response =
mTelephonyManager.iccOpenLogicalChannel("", p2);
final int logicalChannel = response.getChannel();
assertThat(logicalChannel).isEqualTo(INVALID_CHANNEL);
assertThat(response.getStatus()).isNotEqualTo(STATUS_NO_ERROR);
}
}
/**
* Test that it's possible to close logical channels to the ICC. This follows the Manage Channel
* command described in TS 102 221 Section 11.1.17.
*/
@Test
public void testIccCloseLogicalChannel() {
// The directory here doesn't matter - we just need to open a valid connection that can
// later be closed. In this case, the specified AID ("") opens a channel and selects the MF.
IccOpenLogicalChannelResponse response = mTelephonyManager.iccOpenLogicalChannel("");
// Check that the select command succeeded. This ensures that the logical channel is indeed
// open.
assertThat(response.getSelectResponse()).isEqualTo(STATUS_NORMAL);
assertThat(mTelephonyManager.iccCloseLogicalChannel(response.getChannel())).isTrue();
// Close opened channel twice.
try {
boolean result = mTelephonyManager.iccCloseLogicalChannel(response.getChannel());
assertThat(result).isFalse();
} catch (IllegalArgumentException ex) {
//IllegalArgumentException is expected sometimes because of different behaviour of modem
}
// Channel 0 is guaranteed to be always available and cannot be closed, per TS 102 221
// Section 11.1.17
try {
mTelephonyManager.iccCloseLogicalChannel(0);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// IllegalArgumentException is expected
}
}
/**
* This test ensures that valid APDU instructions can be sent and processed by the ICC. To do
* so, APDUs are sent to: - get the status of the MF - select the Access Rule Reference (ARR)
* for the MF - get the FCP template response for the select
*/
@Test
public void testIccTransmitApduLogicalChannel() {
// An open LC is required for transmitting APDU commands. This opens an LC to the MF.
IccOpenLogicalChannelResponse iccOpenLogicalChannelResponse =
mTelephonyManager.iccOpenLogicalChannel("");
// Get the status of the current directory. This should match the MF. TS 102 221 Section
// 11.1.2
final int logicalChannel = iccOpenLogicalChannelResponse.getChannel();
try {
int cla = CLA_STATUS;
int p1 = 0; // no indication of application status
int p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel()
// above
int p3 = 0; // length of 'data' payload
String data = "";
String response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_STATUS, p1, p2, p3, data);
FcpTemplate fcpTemplate = FcpTemplate.parseFcpTemplate(response);
// Check that the FCP Template's file ID matches the MF
assertThat(containsFileId(fcpTemplate, MF_FILE_ID)).isTrue();
assertThat(fcpTemplate.getStatus()).isEqualTo(STATUS_NORMAL_STRING);
// Select the Access Rule Reference for the MF. Similar to the MF, this will exist
// across all SIM cards. TS 102 221 Section 11.1.1
cla = CLA_SELECT;
p1 = 0; // select EF by FID
p2 = 0x04; // requesting FCP template
p3 = 2; // data (FID to be selected) is 2 bytes
data = MF_ARR_FILE_ID;
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_SELECT, p1, p2, p3, data);
// Devices launching with Q or later must immediately return the FCP template from the
// previous SELECT command. Some devices that launched before Q return TPDUs (instead of
// APDUs) - these devices must issue a subsequent GET RESPONSE command to get the FCP
// template.
if (Build.VERSION.DEVICE_INITIAL_SDK_INT < Build.VERSION_CODES.Q) {
// Conditionally need to send GET RESPONSE apdu based on response from
// TelephonyManager
if (response.startsWith(STATUS_BYTES_REMAINING)) {
// Read the FCP template from the ICC. TS 102 221 Section 12.1.1
cla = CLA_GET_RESPONSE;
p1 = 0;
p2 = 0;
p3 = 0;
data = "";
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_GET_RESPONSE, p1, p2, p3, data);
}
}
fcpTemplate = FcpTemplate.parseFcpTemplate(response);
// Check that the FCP Template's file ID matches the selected ARR
assertThat(containsFileId(fcpTemplate, MF_ARR_FILE_ID)).isTrue();
assertThat(fcpTemplate.getStatus()).isEqualTo(STATUS_NORMAL_STRING);
} finally {
mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
}
}
/**
* Tests several invalid APDU instructions over a logical channel and makes sure appropriate
* errors are returned from the UICC.
*/
@Test
public void testIccTransmitApduLogicalChannelWithInvalidInputs() {
// An open LC is required for transmitting apdu commands. This opens an LC to the MF.
IccOpenLogicalChannelResponse iccOpenLogicalChannelResponse =
mTelephonyManager.iccOpenLogicalChannel("");
final int logicalChannel = iccOpenLogicalChannelResponse.getChannel();
try {
// Make some invalid APDU commands and make sure they fail as expected.
// Use an invalid p1 value for Status apdu
int cla = CLA_STATUS | logicalChannel;
int p1 = 0xFF; // only '00', '01', and '02' are allowed
int p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel()
// above
int p3 = 0; // length of 'data' payload
String data = "";
String response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_STATUS, p1, p2, p3, data);
assertThat(INVALID_PARAMETERS_STATUSES.contains(response)).isTrue();
// Select a file that doesn't exist
cla = CLA_SELECT;
p1 = 0x00; // select by file ID
p2 = 0x0C; // no data returned
p3 = 0x02; // length of 'data' payload
data = "FFFF"; // invalid file ID
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_SELECT, p1, p2, p3, data);
assertThat(response).isEqualTo(STATUS_FILE_NOT_FOUND);
// Manage channel with incorrect p1 parameter
cla = CLA_MANAGE_CHANNEL | logicalChannel;
p1 = 0x83; // Only '80' or '00' allowed for Manage Channel p1
p2 = logicalChannel; // channel to be closed
p3 = 0; // length of 'data' payload
data = "";
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_MANAGE_CHANNEL, p1, p2, p3, data);
assertThat(isErrorResponse(response)).isTrue();
// Use an incorrect class byte for Status apdu
cla = 0xFF;
p1 = 0; // no indication of application status
p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel() above
p3 = 0; // length of 'data' payload
data = "";
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_STATUS, p1, p2, p3, data);
assertThat(response).isEqualTo(STATUS_WRONG_CLASS);
// Provide a data field that is longer than described for Select apdu
cla = CLA_SELECT | logicalChannel;
p1 = 0; // select by file ID
p2 = 0x0C; // no data returned
p3 = 0x04; // data passed is actually 2 bytes long
data = "3F00"; // valid ID
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, COMMAND_SELECT, p1, p2, p3, data);
assertThat(isErrorResponse(response)).isTrue();
// Use an invalid instruction
cla = 0;
p1 = 0;
p2 = 0;
p3 = 0;
data = "";
int invalidInstruction = 0xFF; // see TS 102 221 Table 10.5 for valid instructions
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, cla, invalidInstruction, p1, p2, p3, data);
assertThat(isErrorResponse(response)).isTrue();
} finally {
mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
}
}
/**
* This test ensures that files can be read off the UICC. This helps to test the SIM booting
* process, as it process involves several file-reads. The ICCID is one of the first files read.
*/
@Test
public void testApduFileRead() {
// Open a logical channel and select the MF.
IccOpenLogicalChannelResponse iccOpenLogicalChannel =
mTelephonyManager.iccOpenLogicalChannel("");
final int logicalChannel = iccOpenLogicalChannel.getChannel();
try {
// Select the ICCID. TS 102 221 Section 13.2
int p1 = 0; // select by file ID
int p2 = 0x0C; // no data returned
int p3 = 2; // length of 'data' payload
String response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, CLA_SELECT, COMMAND_SELECT, p1, p2, p3, ICCID_FILE_ID);
assertThat(response).isEqualTo(STATUS_NORMAL_STRING);
// Read the contents of the ICCID.
p1 = 0; // 0-byte offset
p2 = 0; // 0-byte offset
p3 = 0; // length of 'data' payload
response =
mTelephonyManager.iccTransmitApduLogicalChannel(
logicalChannel, CLA_READ_BINARY, COMMAND_READ_BINARY, p1, p2, p3, "");
assertThat(response).endsWith(STATUS_NORMAL_STRING);
} finally {
mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
}
}
/** This test sends several valid APDU commands over the basic channel (channel 0). */
@Test
public void testIccTransmitApduBasicChannel() {
// select the MF
int cla = CLA_SELECT;
int p1 = 0; // select EF by FID
int p2 = 0x0C; // requesting FCP template
int p3 = 2; // length of 'data' payload
String data = MF_FILE_ID;
String response =
mTelephonyManager.iccTransmitApduBasicChannel(
cla, COMMAND_SELECT, p1, p2, p3, data);
assertThat(response).isEqualTo(STATUS_NORMAL_STRING);
// get the Status of the current file/directory
cla = CLA_STATUS;
p1 = 0; // no indication of application status
p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel() above
p3 = 0; // length of 'data' payload
data = "";
response =
mTelephonyManager.iccTransmitApduBasicChannel(
cla, COMMAND_STATUS, p1, p2, p3, data);
FcpTemplate fcpTemplate = FcpTemplate.parseFcpTemplate(response);
assertThat(containsFileId(fcpTemplate, MF_FILE_ID)).isTrue();
// Manually open a logical channel
cla = CLA_MANAGE_CHANNEL;
p1 = 0; // open a logical channel
p2 = 0; // '00' for open command
p3 = 0; // length of data payload
data = "";
response =
mTelephonyManager.iccTransmitApduBasicChannel(
cla, COMMAND_MANAGE_CHANNEL, p1, p2, p3, data);
// response is in the format | 1 byte: channel number | 2 bytes: status word |
String responseStatus = response.substring(2);
assertThat(responseStatus).isEqualTo(STATUS_NORMAL_STRING);
// Close the open channel
byte[] responseBytes = hexStringToBytes(response);
int channel = responseBytes[0];
cla = CLA_MANAGE_CHANNEL;
p1 = 0x80; // close a logical channel
p2 = channel; // the channel to be closed
p3 = 0; // length of data payload
data = "";
response =
mTelephonyManager.iccTransmitApduBasicChannel(
cla, COMMAND_MANAGE_CHANNEL, p1, p2, p3, data);
assertThat(response).isEqualTo(STATUS_NORMAL_STRING);
}
/**
* This test verifies that {@link TelephonyManager#setLine1NumberForDisplay(String, String)}
* correctly sets the Line 1 alpha tag and number when called, and the phone number
* of {@link SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER}.
*/
@Test
public void testLine1NumberForDisplay() {
int subId = SubscriptionManager.getDefaultSubscriptionId();
// Cache original alpha tag and number values.
String originalAlphaTag = mTelephonyManager.getLine1AlphaTag();
String originalNumber = mTelephonyManager.getLine1Number();
try {
// clear any potentially overridden values and cache defaults
mTelephonyManager.setLine1NumberForDisplay(null, null);
String defaultAlphaTag = mTelephonyManager.getLine1AlphaTag();
String defaultNumber = mTelephonyManager.getLine1Number();
assertThat(mTelephonyManager.setLine1NumberForDisplay(ALPHA_TAG_A, NUMBER_A)).isTrue();
assertThat(mTelephonyManager.getLine1AlphaTag()).isEqualTo(ALPHA_TAG_A);
assertThat(mTelephonyManager.getLine1Number()).isEqualTo(NUMBER_A);
assertThat(mSubscriptionManager.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER))
.isEqualTo(NUMBER_A);
assertThat(mTelephonyManager.setLine1NumberForDisplay(ALPHA_TAG_B, NUMBER_B)).isTrue();
assertThat(mTelephonyManager.getLine1AlphaTag()).isEqualTo(ALPHA_TAG_B);
assertThat(mTelephonyManager.getLine1Number()).isEqualTo(NUMBER_B);
assertThat(mSubscriptionManager.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER))
.isEqualTo(NUMBER_B);
// null is used to clear the Line 1 alpha tag and number values.
assertThat(mTelephonyManager.setLine1NumberForDisplay(null, null)).isTrue();
assertThat(mTelephonyManager.getLine1AlphaTag()).isEqualTo(defaultAlphaTag);
assertThat(mTelephonyManager.getLine1Number()).isEqualTo(defaultNumber);
assertThat(mSubscriptionManager.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER))
.isEqualTo("");
} finally {
// Reset original alpha tag and number values.
mTelephonyManager.setLine1NumberForDisplay(originalAlphaTag, originalNumber);
}
}
/**
* This test verifies that {@link TelephonyManager#setVoiceMailNumber(String, String)} correctly
* sets the VoiceMail alpha tag and number when called.
*/
@Test
public void testVoiceMailNumber() {
// Cache original alpha tag and number values.
String originalAlphaTag = mTelephonyManager.getVoiceMailAlphaTag();
String originalNumber = mTelephonyManager.getVoiceMailNumber();
try {
assertThat(mTelephonyManager.setVoiceMailNumber(ALPHA_TAG_A, NUMBER_A)).isTrue();
assertThat(mTelephonyManager.getVoiceMailAlphaTag()).isEqualTo(ALPHA_TAG_A);
assertThat(mTelephonyManager.getVoiceMailNumber()).isEqualTo(NUMBER_A);
assertThat(mTelephonyManager.setVoiceMailNumber(ALPHA_TAG_B, NUMBER_B)).isTrue();
assertThat(mTelephonyManager.getVoiceMailAlphaTag()).isEqualTo(ALPHA_TAG_B);
assertThat(mTelephonyManager.getVoiceMailNumber()).isEqualTo(NUMBER_B);
} finally {
// Reset original alpha tag and number values.
mTelephonyManager.setVoiceMailNumber(originalAlphaTag, originalNumber);
}
}
/**
* This test verifies that {@link SubscriptionManager#createSubscriptionGroup(List)} correctly
* create a group with the given subscription id.
*
* <p>This also verifies that {@link SubscriptionManager#removeSubscriptionsFromGroup(List,
* ParcelUuid)} correctly remove the given subscription group.
*/
@Test
public void testCreateAndRemoveSubscriptionGroup() {
// Set subscription group with current sub Id.
int subId = SubscriptionManager.getDefaultSubscriptionId();
List<Integer> subGroup = Arrays.asList(subId);
ParcelUuid uuid = mSubscriptionManager.createSubscriptionGroup(subGroup);
// Getting subscriptions in group.
List<SubscriptionInfo> infoList = mSubscriptionManager.getSubscriptionsInGroup(uuid);
try {
assertThat(infoList).hasSize(1);
assertThat(infoList.get(0).getGroupUuid()).isEqualTo(uuid);
assertThat(infoList.get(0).getSubscriptionId()).isEqualTo(subId);
} finally {
// Verify that the given subGroup has been removed.
mSubscriptionManager.removeSubscriptionsFromGroup(subGroup, uuid);
infoList = mSubscriptionManager.getSubscriptionsInGroup(uuid);
assertThat(infoList).isEmpty();
}
}
@Test
public void testAddSubscriptionToExistingGroupForMultipleSims() {
if (mTelephonyManager.getPhoneCount() < DSDS_PHONE_COUNT
|| mSubscriptionManager.getActiveSubscriptionInfoList().size() < DSDS_PHONE_COUNT) {
// This test requires at least two active subscriptions.
return;
}
// Set subscription group with current sub Id.
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
ParcelUuid uuid = ShellIdentityUtils.invokeMethodWithShellPermissions(mSubscriptionManager,
(sm) -> sm.createSubscriptionGroup(Arrays.asList(subId)));
try {
// Get all active subscriptions.
List<SubscriptionInfo> activeSubInfos =
ShellIdentityUtils.invokeMethodWithShellPermissions(mSubscriptionManager,
(sm) -> sm.getActiveSubscriptionInfoList());
List<Integer> activeSubGroup = getSubscriptionIdList(activeSubInfos);
activeSubGroup.removeIf(id -> id == subId);
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSubscriptionManager,
(sm) -> sm.addSubscriptionsIntoGroup(activeSubGroup, uuid));
List<Integer> infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(
mSubscriptionManager,
(sm) -> getSubscriptionIdList(sm.getSubscriptionsInGroup(uuid)));
activeSubGroup.add(subId);
assertThat(infoList).containsExactlyElementsIn(activeSubGroup);
} finally {
removeSubscriptionsFromGroup(uuid);
}
}
/**
* This test verifies that {@link SubscriptionManager#addSubscriptionsIntoGroup(List,
* ParcelUuid)}} correctly add some additional subscriptions to the existing group.
*
* <p>This test required the device has more than one subscription.
*/
@Test
public void testAddSubscriptionToExistingGroupForEsim() {
// Set subscription group with current sub Id.
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
ParcelUuid uuid = mSubscriptionManager.createSubscriptionGroup(Arrays.asList(subId));
try {
// Get all accessible eSim subscription.
List<SubscriptionInfo> accessibleSubInfos =
mSubscriptionManager.getAccessibleSubscriptionInfoList();
if (accessibleSubInfos != null && accessibleSubInfos.size() > 1) {
List<Integer> accessibleSubGroup = getSubscriptionIdList(accessibleSubInfos);
accessibleSubGroup.removeIf(id -> id == subId);
mSubscriptionManager.addSubscriptionsIntoGroup(accessibleSubGroup, uuid);
List<Integer> infoList =
getSubscriptionIdList(mSubscriptionManager.getSubscriptionsInGroup(uuid));
accessibleSubGroup.add(subId);
assertThat(infoList).containsExactlyElementsIn(accessibleSubGroup);
}
} finally {
removeSubscriptionsFromGroup(uuid);
}
}
/**
* This test verifies that {@link SubscriptionManager#setOpportunistic(boolean, int)} correctly
* set the opportunistic property of the given subscription.
*/
@Test
public void testOpportunistic() {
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(subId);
boolean oldOpportunistic = info.isOpportunistic();
boolean newOpportunistic = !oldOpportunistic;
try {
// Mark the given subscription as opportunistic subscription.
assertThat(mSubscriptionManager.setOpportunistic(newOpportunistic, subId)).isTrue();
// Verify that the given subscription is opportunistic subscription.
info = mSubscriptionManager.getActiveSubscriptionInfo(subId);
assertThat(info.isOpportunistic()).isEqualTo(newOpportunistic);
} finally {
// Set back to original opportunistic property.
mSubscriptionManager.setOpportunistic(oldOpportunistic, subId);
info = mSubscriptionManager.getActiveSubscriptionInfo(subId);
assertThat(info.isOpportunistic()).isEqualTo(oldOpportunistic);
}
}
/**
* This test verifies that {@link TelephonyManager#iccExchangeSimIO(int, int, int, int, int,
* String)} correctly transmits iccIO commands to the UICC card. First, the MF is selected via a
* SELECT apdu via the basic channel, then a STATUS AT-command is sent.
*/
@Test
public void testIccExchangeSimIO() {
// select the MF first. This makes sure the next STATUS AT-command returns a FCP template
// for the right file.
int cla = CLA_SELECT;
int p1 = 0; // select EF by FID
int p2 = 0x0C; // requesting FCP template
int p3 = 2; // length of 'data' payload
String data = MF_FILE_ID;
String response =
mTelephonyManager.iccTransmitApduBasicChannel(
cla, COMMAND_SELECT, p1, p2, p3, data);
assertThat(response).isEqualTo(STATUS_NORMAL_STRING);
// The iccExchangeSimIO command implements the +CRSM command defined in TS 27.007 section
// 8.18. A STATUS command is sent and the returned value will be an FCP template.
byte[] result =
mTelephonyManager.iccExchangeSimIO(
0, // fileId: not required for STATUS
COMMAND_STATUS, // command: STATUS
0, // p1: not required for STATUS
0, // p2: not required for STATUS
0, // p3: not required for STATUS
""); // filePath: not required for STATUS
String resultString = bytesToHexString(result);
FcpTemplate fcpTemplate = FcpTemplate.parseFcpTemplate(resultString);
assertThat(containsFileId(fcpTemplate, MF_FILE_ID)).isTrue();
assertWithMessage("iccExchangeSimIO returned non-normal Status byte: %s", resultString)
.that(fcpTemplate.getStatus())
.isEqualTo(STATUS_NORMAL_STRING);
}
/**
* This test checks that a STATUS apdu can be sent as an encapsulated envelope to the UICC via
* {@link TelephonyManager#sendEnvelopeWithStatus(String)}.
*/
@Test
public void testSendEnvelopeWithStatus() {
// STATUS apdu as hex String
String envelope =
CLA_STATUS_STRING
+ COMMAND_STATUS_STRING
+ "00" // p1: no indication of application status
+ "00"; // p2: identical parameters to
String response = mTelephonyManager.sendEnvelopeWithStatus(envelope);
// TODO(b/137963715): add more specific assertions on response from TelMan#sendEnvelope
assertWithMessage("sendEnvelopeWithStatus is null for envelope=%s", envelope)
.that(response)
.isNotNull();
}
/**
* This test checks that applications with carrier privilege can set/clear signal strength
* update request via {@link
* TelephonyManager#setSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)} and {@link
* TelephonyManager#clearSignalStrengthUpdateRequest} without {@link
* android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*/
@Test
public void testSetClearSignalStrengthUpdateRequest() {
final SignalStrengthUpdateRequest request =
new SignalStrengthUpdateRequest.Builder()
.setSignalThresholdInfos(
List.of(
new SignalThresholdInfo.Builder()
.setRadioAccessNetworkType(
AccessNetworkConstants.AccessNetworkType
.GERAN)
.setSignalMeasurementType(
SignalThresholdInfo
.SIGNAL_MEASUREMENT_TYPE_RSSI)
.setThresholds(new int[] {-113, -103, -97, -51})
.build()))
.setReportingRequestedWhileIdle(true)
.build();
try {
mTelephonyManager.setSignalStrengthUpdateRequest(request);
} finally {
mTelephonyManager.clearSignalStrengthUpdateRequest(request);
}
}
private void verifyValidIccOpenLogicalChannelResponse(IccOpenLogicalChannelResponse response) {
// The assigned channel should be between the min and max allowed channel numbers
int channel = response.getChannel();
assertThat(channel).isIn(Range.closed(MIN_LOGICAL_CHANNEL, MAX_LOGICAL_CHANNEL));
assertThat(response.getStatus()).isEqualTo(STATUS_NO_ERROR);
assertThat(response.getSelectResponse()).isEqualTo(STATUS_NORMAL);
}
private void removeSubscriptionsFromGroup(ParcelUuid uuid) {
List<SubscriptionInfo> infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(
mSubscriptionManager,
(sm) -> (sm.getSubscriptionsInGroup(uuid)));
if (!infoList.isEmpty()) {
List<Integer> subscriptionIdList = getSubscriptionIdList(infoList);
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSubscriptionManager,
(sm) -> sm.removeSubscriptionsFromGroup(subscriptionIdList, uuid));
}
infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(
mSubscriptionManager,
(sm) -> (sm.getSubscriptionsInGroup(uuid)));
assertThat(infoList).isEmpty();
}
private List<Integer> getSubscriptionIdList(List<SubscriptionInfo> subInfoList) {
if (subInfoList == null || subInfoList.isEmpty()) return Collections.EMPTY_LIST;
return subInfoList.stream()
.map(info -> info.getSubscriptionId())
.collect(Collectors.toList());
}
/**
* Checks whether the a {@code fcpTemplate} contains the given {@code fileId}.
*
* @param fcpTemplate The FCP Template to be checked.
* @param fileId The file ID that is being searched for
* @return true iff fcpTemplate contains fileId.
*/
private boolean containsFileId(FcpTemplate fcpTemplate, String fileId) {
return fcpTemplate.getTlvs().stream()
.anyMatch(tlv -> tlv.getTag() == FILE_IDENTIFIER && tlv.getValue().equals(fileId));
}
/**
* Returns true iff {@code response} indicates an error with the previous APDU.
*
* @param response The APDU response to be checked.
* @return true iff the given response indicates an error occurred
*/
private boolean isErrorResponse(@Nonnull String response) {
return !(STATUS_NORMAL_STRING.equals(response)
|| response.startsWith(STATUS_WARNING_A)
|| response.startsWith(STATUS_WARNING_B)
|| response.startsWith(STATUS_BYTES_REMAINING));
}
private static class IntentReceiver extends BroadcastReceiver {
private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
@Override
public void onReceive(Context context, Intent intent) {
mReceiveLatch.countDown();
}
public boolean waitForReceive() throws InterruptedException {
return mReceiveLatch.await(30, TimeUnit.SECONDS);
}
}
@Test
public void testEapSimAuthentication() {
assumeTrue(
"testEapSimAuthentication requires a 2021 CTS UICC or newer",
UiccUtil.uiccHasCertificate(CTS_UICC_2021));
// K: '000102030405060708090A0B0C0D0E0F', defined by TS 134 108#8.2
// n: 128 (Bits to use for RES value)
// Format: [Length][RAND]
String challenge = "10" + EAP_SIM_AKA_RAND;
String base64Challenge = Base64.encodeToString(hexStringToBytes(challenge), Base64.NO_WRAP);
String base64Response =
mTelephonyManager.getIccAuthentication(
TelephonyManager.APPTYPE_USIM,
TelephonyManager.AUTHTYPE_EAP_SIM,
base64Challenge);
byte[] response = Base64.decode(base64Response, Base64.DEFAULT);
assertWithMessage("Results for AUTHTYPE_EAP_SIM failed")
.that(response)
.isEqualTo(hexStringToBytes(EXPECTED_EAP_SIM_RESULT));
}
@Test
public void testEapAkaAuthentication() {
assumeTrue(
"testEapAkaAuthentication requires a 2021 CTS UICC or newer",
UiccUtil.uiccHasCertificate(CTS_UICC_2021));
// K: '000102030405060708090A0B0C0D0E0F', defined by TS 134 108#8.2
// n: 128 (Bits to use for RES value)
// Format: [Length][Rand][Length][Autn]
String challenge = "10" + EAP_SIM_AKA_RAND + "10" + EAP_AKA_AUTN;
String base64Challenge = Base64.encodeToString(hexStringToBytes(challenge), Base64.NO_WRAP);
String base64Response =
mTelephonyManager.getIccAuthentication(
TelephonyManager.APPTYPE_USIM,
TelephonyManager.AUTHTYPE_EAP_AKA,
base64Challenge);
assertWithMessage("UICC returned null for EAP-AKA auth").that(base64Response).isNotNull();
byte[] response = Base64.decode(base64Response, Base64.NO_WRAP);
// response may be formatted as: [DB][Length][RES][Length][CK][Length][IK][Length][Kc]
byte[] akaResponse = Arrays.copyOfRange(response, 0, EAP_AKA_RESPONSE_LENGTH);
assertWithMessage("Results for AUTHTYPE_EAP_AKA failed")
.that(akaResponse)
.isEqualTo(hexStringToBytes(EXPECTED_EAP_AKA_RESULT));
}
/**
* This test checks that applications with carrier privilege can set/get data enable
* state.
*/
@Test
public void testDataEnableRequest() {
for (int i = DATA_ENABLED_REASON_USER; i <= DATA_ENABLED_REASON_THERMAL; i++) {
mTelephonyManager.isDataEnabledForReason(i);
}
boolean isDataEnabled = mTelephonyManager.isDataEnabledForReason(
TelephonyManager.DATA_ENABLED_REASON_CARRIER);
mTelephonyManager.setDataEnabledForReason(
TelephonyManager.DATA_ENABLED_REASON_CARRIER, !isDataEnabled);
mTelephonyManager.setDataEnabledForReason(
TelephonyManager.DATA_ENABLED_REASON_CARRIER, isDataEnabled);
}
/**
* This test checks that applications with carrier privileges can get network slicing
* configuration.
*/
@Test
public void testGetNetworkSlicingConfiguration() {
CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
mTelephonyManager.getNetworkSlicingConfiguration(
AsyncTask.SERIAL_EXECUTOR, resultFuture::complete);
}
}