Verify carrier privileges for VCN-providing packages

This change adds and verifies packages in the VcnManagmentService,
ensuring that carrier privilege gain/loss correctly starts/tears down
VCN instances.

Bug: 163431877
Test: atest FrameworksNetTests
Change-Id: I63203188c57fdde1cfc58aaf1108aa3e70eb4a50
This commit is contained in:
Benedict Wong
2020-11-30 17:25:46 -08:00
parent 6153723db5
commit 9cb5807845
8 changed files with 261 additions and 65 deletions

View File

@@ -23,6 +23,6 @@ import android.os.ParcelUuid;
* @hide
*/
interface IVcnManagementService {
void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config);
void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
void clearVcnConfig(in ParcelUuid subscriptionGroup);
}

View File

@@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -45,11 +46,17 @@ import java.util.Set;
public final class VcnConfig implements Parcelable {
@NonNull private static final String TAG = VcnConfig.class.getSimpleName();
private static final String PACKAGE_NAME_KEY = "mPackageName";
@NonNull private final String mPackageName;
private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
@NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) {
mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs);
private VcnConfig(
@NonNull String packageName,
@NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
mPackageName = packageName;
mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
validate();
}
@@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable {
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
public VcnConfig(@NonNull PersistableBundle in) {
mPackageName = in.getString(PACKAGE_NAME_KEY);
final PersistableBundle gatewayConnectionConfigsBundle =
in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
mGatewayConnectionConfigs =
@@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable {
}
private void validate() {
Objects.requireNonNull(mPackageName, "packageName was null");
Preconditions.checkCollectionNotEmpty(
mGatewayConnectionConfigs, "gatewayConnectionConfigs");
mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
}
/**
* Retrieve the package name of the provisioning app.
*
* @hide
*/
@NonNull
public String getProvisioningPackageName() {
return mPackageName;
}
/** Retrieves the set of configured tunnels. */
@@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable {
public PersistableBundle toPersistableBundle() {
final PersistableBundle result = new PersistableBundle();
result.putString(PACKAGE_NAME_KEY, mPackageName);
final PersistableBundle gatewayConnectionConfigsBundle =
PersistableBundleUtils.fromList(
new ArrayList<>(mGatewayConnectionConfigs),
@@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mGatewayConnectionConfigs);
return Objects.hash(mPackageName, mGatewayConnectionConfigs);
}
@Override
@@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable {
}
final VcnConfig rhs = (VcnConfig) other;
return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
return mPackageName.equals(rhs.mPackageName)
&& mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
}
// Parcelable methods
@@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable {
/** This class is used to incrementally build {@link VcnConfig} objects. */
public static class Builder {
@NonNull private final String mPackageName;
@NonNull
private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
public Builder(@NonNull Context context) {
Objects.requireNonNull(context, "context was null");
mPackageName = context.getOpPackageName();
}
/**
* Adds a configuration for an individual gateway connection.
*
@@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable {
*/
@NonNull
public VcnConfig build() {
return new VcnConfig(mGatewayConnectionConfigs);
return new VcnConfig(mPackageName, mGatewayConnectionConfigs);
}
}
}

View File

@@ -101,7 +101,7 @@ public final class VcnManager {
requireNonNull(config, "config was null");
try {
mService.setVcnConfig(subscriptionGroup, config);
mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
} catch (ServiceSpecificException e) {
throw new IOException(e);
} catch (RemoteException e) {

View File

@@ -22,6 +22,7 @@ import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubsc
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkProvider;
@@ -122,6 +123,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";
// TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
@@ -346,9 +348,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
// Start any VCN instances as necessary
for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
if (snapshot.getActiveSubscriptionGroups().contains(entry.getKey())) {
// TODO: Add checks to ensure provisioning app is currently carrier
// privileged on this subscription
if (snapshot.packageHasPermissionsForSubscriptionGroup(
entry.getKey(), entry.getValue().getProvisioningPackageName())) {
if (!mVcns.containsKey(entry.getKey())) {
startVcnLocked(entry.getKey(), entry.getValue());
}
@@ -361,7 +362,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
// Schedule teardown of any VCN instances that have lost carrier privileges (after a
// delay)
for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
if (!snapshot.getActiveSubscriptionGroups().contains(entry.getKey())) {
final VcnConfig config = mConfigs.get(entry.getKey());
if (config == null
|| !snapshot.packageHasPermissionsForSubscriptionGroup(
entry.getKey(), config.getProvisioningPackageName())) {
final ParcelUuid uuidToTeardown = entry.getKey();
final Vcn instanceToTeardown = entry.getValue();
@@ -408,11 +412,20 @@ public class VcnManagementService extends IVcnManagementService.Stub {
* <p>Implements the IVcnManagementService Binder interface.
*/
@Override
public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
public void setVcnConfig(
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
@NonNull String opPkgName) {
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
requireNonNull(config, "config was null");
requireNonNull(opPkgName, "opPkgName was null");
if (!config.getProvisioningPackageName().equals(opPkgName)) {
throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
}
Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup);
mContext.getSystemService(AppOpsManager.class)
.checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
Binder.withCleanCallingIdentity(() -> {

View File

@@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
@NonNull private final TelephonySubscriptionTrackerCallback mCallback;
@NonNull private final Dependencies mDeps;
@NonNull private final TelephonyManager mTelephonyManager;
@NonNull private final SubscriptionManager mSubscriptionManager;
@NonNull private final CarrierConfigManager mCarrierConfigManager;
@@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
mCallback = Objects.requireNonNull(callback, "Missing callback");
mDeps = Objects.requireNonNull(deps, "Missing deps");
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
@@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
* so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
*/
public void handleSubscriptionsChanged() {
final Set<ParcelUuid> activeSubGroups = new ArraySet<>();
final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>();
final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
@@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
// group.
if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
&& mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
activeSubGroups.add(subInfo.getGroupUuid());
// TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker
final TelephonyManager subIdSpecificTelephonyManager =
mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
final ParcelUuid subGroup = subInfo.getGroupUuid();
final Set<String> pkgs =
privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());
privilegedPackages.put(subGroup, pkgs);
}
}
final TelephonySubscriptionSnapshot newSnapshot =
new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups);
new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages);
// If snapshot was meaningfully updated, fire the callback
if (!newSnapshot.equals(mCurrentSnapshot)) {
@@ -231,25 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
/** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
public static class TelephonySubscriptionSnapshot {
private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
private final Set<ParcelUuid> mActiveGroups;
private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptySet());
new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap());
@VisibleForTesting(visibility = Visibility.PRIVATE)
TelephonySubscriptionSnapshot(
@NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
@NonNull Set<ParcelUuid> activeGroups) {
mSubIdToGroupMap = Collections.unmodifiableMap(
Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"));
mActiveGroups = Collections.unmodifiableSet(
Objects.requireNonNull(activeGroups, "activeGroups was null"));
@NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null");
Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap);
final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
unmodifiableInnerSets.put(
entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
}
mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
}
/** Returns the active subscription groups */
@NonNull
public Set<ParcelUuid> getActiveSubscriptionGroups() {
return mActiveGroups;
return mPrivilegedPackages.keySet();
}
/** Checks if the provided package is carrier privileged for the specified sub group. */
public boolean packageHasPermissionsForSubscriptionGroup(
@NonNull ParcelUuid subGrp, @NonNull String packageName) {
final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);
return privilegedPackages != null && privilegedPackages.contains(packageName);
}
/** Returns the Subscription Group for a given subId. */
@@ -276,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
@Override
public int hashCode() {
return Objects.hash(mSubIdToGroupMap, mActiveGroups);
return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages);
}
@Override
@@ -288,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
return mSubIdToGroupMap.equals(other.mSubIdToGroupMap)
&& mActiveGroups.equals(other.mActiveGroups);
&& mPrivilegedPackages.equals(other.mPrivilegedPackages);
}
@Override
public String toString() {
return "TelephonySubscriptionSnapshot{ "
+ "mSubIdToGroupMap=" + mSubIdToGroupMap
+ ", mPrivilegedPackages=" + mPrivilegedPackages
+ " }";
}
}

View File

@@ -18,12 +18,17 @@ package android.net.vcn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,12 +38,15 @@ import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnConfigTest {
private static final String TEST_PACKAGE_NAME = VcnConfigTest.class.getPackage().getName();
private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
private final Context mContext = mock(Context.class);
// Public visibility for VcnManagementServiceTest
public static VcnConfig buildTestConfig() {
VcnConfig.Builder builder = new VcnConfig.Builder();
public static VcnConfig buildTestConfig(@NonNull Context context) {
VcnConfig.Builder builder = new VcnConfig.Builder(context);
for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
builder.addGatewayConnectionConfig(gatewayConnectionConfig);
@@ -47,10 +55,24 @@ public class VcnConfigTest {
return builder.build();
}
@Before
public void setUp() throws Exception {
doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
}
@Test
public void testBuilderConstructorRequiresContext() {
try {
new VcnConfig.Builder(null);
fail("Expected exception due to null context");
} catch (NullPointerException e) {
}
}
@Test
public void testBuilderRequiresGatewayConnectionConfig() {
try {
new VcnConfig.Builder().build();
new VcnConfig.Builder(mContext).build();
fail("Expected exception due to no VcnGatewayConnectionConfigs provided");
} catch (IllegalArgumentException e) {
}
@@ -58,21 +80,22 @@ public class VcnConfigTest {
@Test
public void testBuilderAndGetters() {
final VcnConfig config = buildTestConfig();
final VcnConfig config = buildTestConfig(mContext);
assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
}
@Test
public void testPersistableBundle() {
final VcnConfig config = buildTestConfig();
final VcnConfig config = buildTestConfig(mContext);
assertEquals(config, new VcnConfig(config.toPersistableBundle()));
}
@Test
public void testParceling() {
final VcnConfig config = buildTestConfig();
final VcnConfig config = buildTestConfig(mContext);
Parcel parcel = Parcel.obtain();
config.writeToParcel(parcel, 0);

View File

@@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -31,6 +32,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.vcn.VcnConfig;
@@ -67,9 +69,20 @@ import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnManagementServiceTest {
private static final String TEST_PACKAGE_NAME =
VcnManagementServiceTest.class.getPackage().getName();
private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
private static final VcnConfig TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig();
private static final VcnConfig TEST_VCN_CONFIG;
private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
static {
final Context mockConfigContext = mock(Context.class);
doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName();
TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext);
}
private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
@@ -104,6 +117,7 @@ public class VcnManagementServiceTest {
private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class);
private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class);
private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class);
private final VcnContext mVcnContext = mock(VcnContext.class);
private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper =
mock(PersistableBundleUtils.LockingReadWriteHelper.class);
@@ -117,9 +131,12 @@ public class VcnManagementServiceTest {
setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
setupSystemService(
mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class);
setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class);
doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid();
doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
doReturn(mVcnContext)
.when(mMockDeps)
.newVcnContext(
@@ -213,6 +230,16 @@ public class VcnManagementServiceTest {
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
final Set<String> privilegedPackages =
(activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty())
? Collections.emptySet()
: Collections.singleton(TEST_PACKAGE_NAME);
doReturn(true)
.when(snapshot)
.packageHasPermissionsForSubscriptionGroup(
argThat(val -> activeSubscriptionGroups.contains(val)),
eq(TEST_PACKAGE_NAME));
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
cb.onNewSnapshot(snapshot);
}
@@ -227,7 +254,7 @@ public class VcnManagementServiceTest {
}
private Vcn startAndGetVcnInstance(ParcelUuid uuid) {
mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG);
mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
return mVcnMgmtSvc.getAllVcns().get(uuid);
}
@@ -302,7 +329,7 @@ public class VcnManagementServiceTest {
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
try {
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
fail("Expected IllegalStateException exception for system server");
} catch (IllegalStateException expected) {
}
@@ -310,12 +337,12 @@ public class VcnManagementServiceTest {
@Test
public void testSetVcnConfigRequiresSystemUser() throws Exception {
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
.when(mMockDeps)
.getBinderCallingUid();
try {
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
fail("Expected security exception for non system user");
} catch (SecurityException expected) {
}
@@ -326,16 +353,25 @@ public class VcnManagementServiceTest {
setupMockedCarrierPrivilege(false);
try {
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, VcnConfigTest.buildTestConfig());
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
fail("Expected security exception for missing carrier privileges");
} catch (SecurityException expected) {
}
}
@Test
public void testSetVcnConfigMismatchedPackages() throws Exception {
try {
mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage");
fail("Expected exception due to mismatched packages in config and method call");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testSetVcnConfig() throws Exception {
// Use a different UUID to simulate a new VCN config.
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG);
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
}
@@ -353,7 +389,7 @@ public class VcnManagementServiceTest {
@Test
public void testClearVcnConfigRequiresSystemUser() throws Exception {
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
.when(mMockDeps)
.getBinderCallingUid();
@@ -385,7 +421,7 @@ public class VcnManagementServiceTest {
@Test
public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception {
// Use a different UUID to simulate a new VCN config.
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG);
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2);
assertEquals(1, vcnInstances.size());
@@ -396,7 +432,7 @@ public class VcnManagementServiceTest {
verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG));
// Verify Vcn is updated if it was previously started
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG);
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
verify(vcnInstance).updateConfig(TEST_VCN_CONFIG);
// Verify Vcn is stopped if it was already started

View File

@@ -30,6 +30,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -37,6 +38,10 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonMap;
import android.annotation.NonNull;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +54,7 @@ import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -63,6 +69,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -71,12 +78,16 @@ import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TelephonySubscriptionTrackerTest {
private static final String PACKAGE_NAME =
TelephonySubscriptionTrackerTest.class.getPackage().getName();
private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
private static final int TEST_SIM_SLOT_INDEX = 1;
private static final int TEST_SUBSCRIPTION_ID_1 = 2;
private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class);
private static final int TEST_SUBSCRIPTION_ID_2 = 3;
private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class);
private static final Map<ParcelUuid, Set<String>> TEST_PRIVILEGED_PACKAGES =
Collections.singletonMap(TEST_PARCEL_UUID, Collections.singleton(PACKAGE_NAME));
private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP;
static {
@@ -91,6 +102,7 @@ public class TelephonySubscriptionTrackerTest {
@NonNull private final Handler mHandler;
@NonNull private final TelephonySubscriptionTracker.Dependencies mDeps;
@NonNull private final TelephonyManager mTelephonyManager;
@NonNull private final SubscriptionManager mSubscriptionManager;
@NonNull private final CarrierConfigManager mCarrierConfigManager;
@@ -103,9 +115,15 @@ public class TelephonySubscriptionTrackerTest {
mHandler = new Handler(mTestLooper.getLooper());
mDeps = mock(TelephonySubscriptionTracker.Dependencies.class);
mTelephonyManager = mock(TelephonyManager.class);
mSubscriptionManager = mock(SubscriptionManager.class);
mCarrierConfigManager = mock(CarrierConfigManager.class);
doReturn(Context.TELEPHONY_SERVICE)
.when(mContext)
.getSystemServiceName(TelephonyManager.class);
doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
.when(mContext)
.getSystemServiceName(SubscriptionManager.class);
@@ -140,6 +158,9 @@ public class TelephonySubscriptionTrackerTest {
doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
.when(mSubscriptionManager)
.getAllSubscriptionInfoList();
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
setPrivilegedPackagesForMock(Collections.singletonList(PACKAGE_NAME));
}
private IntentFilter getIntentFilter() {
@@ -167,13 +188,15 @@ public class TelephonySubscriptionTrackerTest {
return intent;
}
private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) {
return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups);
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
Map<ParcelUuid, Set<String>> privilegedPackages) {
return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages);
}
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) {
return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups);
Map<Integer, ParcelUuid> subIdToGroupMap,
Map<ParcelUuid, Set<String>> privilegedPackages) {
return new TelephonySubscriptionSnapshot(subIdToGroupMap, privilegedPackages);
}
private void verifyNoActiveSubscriptions() {
@@ -186,6 +209,10 @@ public class TelephonySubscriptionTrackerTest {
Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1));
}
private void setPrivilegedPackagesForMock(@NonNull List<String> privilegedPackages) {
doReturn(privilegedPackages).when(mTelephonyManager).getPackagesWithCarrierPrivileges();
}
@Test
public void testRegister() throws Exception {
verify(mContext)
@@ -223,15 +250,30 @@ public class TelephonySubscriptionTrackerTest {
}
@Test
public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception {
public void testOnSubscriptionsChangedFired_WithReadySubidsNoPrivilegedPackages()
throws Exception {
setupReadySubIds();
setPrivilegedPackagesForMock(Collections.emptyList());
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
listener.onSubscriptionsChanged();
mTestLooper.dispatchAll();
final Map<ParcelUuid, Set<String>> privilegedPackages =
Collections.singletonMap(TEST_PARCEL_UUID, new ArraySet<>());
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(privilegedPackages)));
}
@Test
public void testOnSubscriptionsChangedFired_WithReadySubidsAndPrivilegedPackages()
throws Exception {
setupReadySubIds();
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
listener.onSubscriptionsChanged();
mTestLooper.dispatchAll();
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
}
@Test
@@ -239,8 +281,7 @@ public class TelephonySubscriptionTrackerTest {
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
mTestLooper.dispatchAll();
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
}
@Test
@@ -253,8 +294,7 @@ public class TelephonySubscriptionTrackerTest {
mTestLooper.dispatchAll();
// Expect an empty snapshot
verify(mCallback).onNewSnapshot(
eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
}
@Test
@@ -281,41 +321,57 @@ public class TelephonySubscriptionTrackerTest {
@Test
public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
assertNotNull(
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(
eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
}
@Test
public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
assertNotNull(
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet())));
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap())));
assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
}
@Test
public void testChangingPrivilegedPackagesAfterValidTriggersCallbacks() throws Exception {
setupReadySubIds();
// Setup initial "valid" state
final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
listener.onSubscriptionsChanged();
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
// Simulate a loss of carrier privileges
setPrivilegedPackagesForMock(Collections.emptyList());
listener.onSubscriptionsChanged();
mTestLooper.dispatchAll();
verify(mCallback)
.onNewSnapshot(
eq(buildExpectedSnapshot(singletonMap(TEST_PARCEL_UUID, emptySet()))));
}
@Test
public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
final TelephonySubscriptionSnapshot snapshot =
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap());
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1));
assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
@@ -324,7 +380,7 @@ public class TelephonySubscriptionTrackerTest {
@Test
public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
final TelephonySubscriptionSnapshot snapshot =
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, emptyMap());
assertEquals(
new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),