diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index 9dd01140b4139..04b585cdf4208 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -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); } diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index d4a3fa7411b18..ede8faaaf261d 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -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 mGatewayConnectionConfigs; - private VcnConfig(@NonNull Set tunnelConfigs) { - mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs); + private VcnConfig( + @NonNull String packageName, + @NonNull Set 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 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); } } } diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 19c183f9fe9ca..b881a339535b0 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -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) { diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 56bd1a82b3021..77e8e96a515cc 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -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 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 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 { *

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(() -> { diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 9c9ca3df47bf2..d8a145d9ae339 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -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 activeSubGroups = new ArraySet<>(); + final Map> privilegedPackages = new HashMap<>(); final Map newSubIdToGroupMap = new HashMap<>(); final List 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 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 mSubIdToGroupMap; - private final Set mActiveGroups; + private final Map> 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 subIdToGroupMap, - @NonNull Set activeGroups) { - mSubIdToGroupMap = Collections.unmodifiableMap( - Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null")); - mActiveGroups = Collections.unmodifiableSet( - Objects.requireNonNull(activeGroups, "activeGroups was null")); + @NonNull Map> privilegedPackages) { + Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"); + Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); + + mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap); + + final Map> unmodifiableInnerSets = new ArrayMap<>(); + for (Entry> entry : privilegedPackages.entrySet()) { + unmodifiableInnerSets.put( + entry.getKey(), Collections.unmodifiableSet(entry.getValue())); + } + mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets); } /** Returns the active subscription groups */ @NonNull public Set 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 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 + + " }"; } } diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java index 77944deb26f1c..c1ef350e5c4ae 100644 --- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java @@ -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 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); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 04091bc05514d..d2caa8f0beeb2 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -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 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 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 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 diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 17b8f64a13fa9..528f240b99126 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -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> TEST_PRIVILEGED_PACKAGES = + Collections.singletonMap(TEST_PARCEL_UUID, Collections.singleton(PACKAGE_NAME)); private static final Map 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 activeSubGroups) { - return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups); + private TelephonySubscriptionSnapshot buildExpectedSnapshot( + Map> privilegedPackages) { + return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, privilegedPackages); } private TelephonySubscriptionSnapshot buildExpectedSnapshot( - Map subIdToGroupMap, Set activeSubGroups) { - return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups); + Map subIdToGroupMap, + Map> 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 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> 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 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 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 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 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)),