/* * Copyright (C) 2020 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 com.android.server; import static android.net.ConnectivityManager.NetworkCallback; import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.Uri; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnConfigTest; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkPolicy; import android.os.IBinder; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.VcnManagementService.VcnStatusCallbackInfo; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.FileNotFoundException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; /** Tests for {@link VcnManagementService}. */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { private static final String CONTEXT_ATTRIBUTION_TAG = "VCN"; private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); private static final String TEST_PACKAGE_NAME_2 = "TEST_PKG_2"; private static final String TEST_CB_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName() + ".callback"; 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 ParcelUuid TEST_UUID_3 = new ParcelUuid(new UUID(2, 2)); private static final VcnConfig TEST_VCN_CONFIG; private static final VcnConfig TEST_VCN_CONFIG_PKG_2; private static final int TEST_UID = Process.FIRST_APPLICATION_UID; private static final String TEST_IFACE_NAME = "TEST_IFACE"; private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2"; private static final LinkProperties TEST_LP_1 = new LinkProperties(); private static final LinkProperties TEST_LP_2 = new LinkProperties(); static { TEST_LP_1.setInterfaceName(TEST_IFACE_NAME); TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2); } static { final Context mockConfigContext = mock(Context.class); doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName(); TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext); doReturn(TEST_PACKAGE_NAME_2).when(mockConfigContext).getOpPackageName(); TEST_VCN_CONFIG_PKG_2 = VcnConfigTest.buildTestConfig(mockConfigContext); } private static final Map TEST_VCN_CONFIG_MAP = Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG)); private static final int TEST_SUBSCRIPTION_ID = 1; private static final int TEST_SUBSCRIPTION_ID_2 = 2; private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO = new SubscriptionInfo( TEST_SUBSCRIPTION_ID /* id */, "" /* iccId */, 0 /* simSlotIndex */, "Carrier" /* displayName */, "Carrier" /* carrierName */, 0 /* nameSource */, 255 /* iconTint */, "12345" /* number */, 0 /* roaming */, null /* icon */, "0" /* mcc */, "0" /* mnc */, "0" /* countryIso */, false /* isEmbedded */, null /* nativeAccessRules */, null /* cardString */, false /* isOpportunistic */, TEST_UUID_1.toString() /* groupUUID */, 0 /* carrierId */, 0 /* profileClass */); private final Context mMockContextWithoutAttributionTag = mock(Context.class); private final Context mMockContext = mock(Context.class); private final VcnManagementService.Dependencies mMockDeps = mock(VcnManagementService.Dependencies.class); private final TestLooper mTestLooper = new TestLooper(); 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); private final TelephonySubscriptionTracker mSubscriptionTracker = mock(TelephonySubscriptionTracker.class); private final ArgumentCaptor mVcnCallbackCaptor = ArgumentCaptor.forClass(VcnCallback.class); private final VcnManagementService mVcnMgmtSvc; private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener = mock(IVcnUnderlyingNetworkPolicyListener.class); private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class); private final IBinder mMockIBinder = mock(IBinder.class); public VcnManagementServiceTest() throws Exception { doReturn(mMockContext) .when(mMockContextWithoutAttributionTag) .createAttributionContext(CONTEXT_ATTRIBUTION_TAG); setupSystemService( mMockContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService( mMockContext, mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( mMockContext, mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); setupSystemService(mMockContext, mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); doReturn(mMockContext).when(mVcnContext).getContext(); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid(); doReturn(mVcnContext) .when(mMockDeps) .newVcnContext( eq(mMockContext), eq(mTestLooper.getLooper()), any(VcnNetworkProvider.class), anyBoolean()); doReturn(mSubscriptionTracker) .when(mMockDeps) .newTelephonySubscriptionTracker( eq(mMockContext), eq(mTestLooper.getLooper()), any(TelephonySubscriptionTrackerCallback.class)); doReturn(mConfigReadWriteHelper) .when(mMockDeps) .newPersistableBundleLockingReadWriteHelper(any()); // Setup VCN instance generation doAnswer((invocation) -> { // Mock-within a doAnswer is safe, because it doesn't actually run nested. return mock(Vcn.class); }).when(mMockDeps).newVcn(any(), any(), any(), any(), any()); final PersistableBundle bundle = PersistableBundleUtils.fromMap( TEST_VCN_CONFIG_MAP, PersistableBundleUtils::fromParcelUuid, VcnConfig::toPersistableBundle); doReturn(bundle).when(mConfigReadWriteHelper).readFromDisk(); setupMockedCarrierPrivilege(true); mVcnMgmtSvc = new VcnManagementService(mMockContextWithoutAttributionTag, mMockDeps); setupActiveSubscription(TEST_UUID_1); doReturn(mMockIBinder).when(mMockPolicyListener).asBinder(); doReturn(mMockIBinder).when(mMockStatusCallback).asBinder(); // Make sure the profiles are loaded. mTestLooper.dispatchAll(); } @Before public void setUp() { doNothing() .when(mMockContext) .enforceCallingOrSelfPermission( eq(android.Manifest.permission.NETWORK_FACTORY), any()); doReturn(Collections.singleton(TRANSPORT_WIFI)) .when(mMockDeps) .getRestrictedTransports(any(), any(), any()); } private void setupMockedCarrierPrivilege(boolean isPrivileged) { setupMockedCarrierPrivilege(isPrivileged, TEST_PACKAGE_NAME); } private void setupMockedCarrierPrivilege(boolean isPrivileged, String pkg) { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) .getSubscriptionsInGroup(any()); doReturn(mTelMgr) .when(mTelMgr) .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId())); doReturn( isPrivileged ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS : CARRIER_PRIVILEGE_STATUS_NO_ACCESS) .when(mTelMgr) .checkCarrierPrivilegesForPackage(eq(pkg)); } @Test public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); verify(mSubscriptionTracker).register(); verify(mConnMgr) .registerNetworkCallback( eq(new NetworkRequest.Builder().clearCapabilities().build()), any(NetworkCallback.class)); } @Test public void testNonSystemServerRealConfigFileAccessPermission() throws Exception { // Attempt to build a real instance of the dependencies, and verify we cannot write to the // file. VcnManagementService.Dependencies deps = new VcnManagementService.Dependencies(); PersistableBundleUtils.LockingReadWriteHelper configReadWriteHelper = deps.newPersistableBundleLockingReadWriteHelper( VcnManagementService.VCN_CONFIG_FILE); // Even tests should not be able to read/write configs from disk; SELinux policies restrict // it to only the system server. // Reading config should always return null since the file "does not exist", and writing // should throw an IOException. assertNull(configReadWriteHelper.readFromDisk()); try { configReadWriteHelper.writeToDisk(new PersistableBundle()); fail("Expected IOException due to SELinux policy"); } catch (FileNotFoundException expected) { } } @Test public void testLoadVcnConfigsOnStartup() throws Exception { mTestLooper.dispatchAll(); assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs()); verify(mConfigReadWriteHelper).readFromDisk(); } private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( ParcelUuid activeDataSubGrp, Set activeSubscriptionGroups) { return triggerSubscriptionTrackerCbAndGetSnapshot( activeDataSubGrp, activeSubscriptionGroups, Collections.emptyMap()); } private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( ParcelUuid activeDataSubGrp, Set activeSubscriptionGroups, Map subIdToGroupMap) { return triggerSubscriptionTrackerCbAndGetSnapshot( activeDataSubGrp, activeSubscriptionGroups, subIdToGroupMap, true /* hasCarrierPrivileges */); } private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( ParcelUuid activeDataSubGrp, Set activeSubscriptionGroups, Map subIdToGroupMap, boolean hasCarrierPrivileges) { return triggerSubscriptionTrackerCbAndGetSnapshot( TEST_SUBSCRIPTION_ID, activeDataSubGrp, activeSubscriptionGroups, subIdToGroupMap, hasCarrierPrivileges); } private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( int activeDataSubId, ParcelUuid activeDataSubGrp, Set activeSubscriptionGroups, Map subIdToGroupMap, boolean hasCarrierPrivileges) { final TelephonySubscriptionSnapshot snapshot = buildSubscriptionSnapshot( activeDataSubId, activeDataSubGrp, activeSubscriptionGroups, subIdToGroupMap, hasCarrierPrivileges); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); cb.onNewSnapshot(snapshot); return snapshot; } private TelephonySubscriptionSnapshot buildSubscriptionSnapshot( int activeDataSubId, ParcelUuid activeDataSubGrp, Set activeSubscriptionGroups, Map subIdToGroupMap, boolean hasCarrierPrivileges) { final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups(); doReturn(activeDataSubGrp).when(snapshot).getActiveDataSubscriptionGroup(); doReturn(activeDataSubId).when(snapshot).getActiveDataSubscriptionId(); final Set privilegedPackages = (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty()) ? Collections.emptySet() : Collections.singleton(TEST_PACKAGE_NAME); doReturn(hasCarrierPrivileges) .when(snapshot) .packageHasPermissionsForSubscriptionGroup( argThat(val -> activeSubscriptionGroups.contains(val)), eq(TEST_PACKAGE_NAME)); doAnswer(invocation -> { return subIdToGroupMap.get(invocation.getArgument(0)); }).when(snapshot).getGroupForSubId(anyInt()); doAnswer(invocation -> { final ParcelUuid subGrp = invocation.getArgument(0); final Set subIds = new ArraySet<>(); for (Entry entry : subIdToGroupMap.entrySet()) { if (entry.getValue().equals(subGrp)) { subIds.add(entry.getKey()); } } return subIds; }).when(snapshot).getAllSubIdsInGroup(any()); return snapshot; } private void setupActiveSubscription(ParcelUuid activeDataSubGrp) { mVcnMgmtSvc.setLastSnapshot( buildSubscriptionSnapshot( TEST_SUBSCRIPTION_ID, activeDataSubGrp, Collections.emptySet(), Collections.emptyMap(), true /* hasCarrierPrivileges */)); } private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() { final ArgumentCaptor captor = ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class); verify(mMockDeps) .newTelephonySubscriptionTracker( eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture()); return captor.getValue(); } private BroadcastReceiver getPackageChangeReceiver() { final ArgumentCaptor captor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext).registerReceiver(captor.capture(), argThat(filter -> { return filter.hasAction(Intent.ACTION_PACKAGE_ADDED) && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED) && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED) && filter.hasAction(Intent.ACTION_PACKAGE_DATA_CLEARED) && filter.hasAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); }), any(), any()); return captor.getValue(); } private Vcn startAndGetVcnInstance(ParcelUuid uuid) { mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); return mVcnMgmtSvc.getAllVcns().get(uuid); } @Test public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { // Add a record for a non-active SIM mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_1, new ArraySet<>(Arrays.asList(TEST_UUID_1, TEST_UUID_2))); verify(mMockDeps) .newVcnContext( eq(mMockContext), eq(mTestLooper.getLooper()), any(VcnNetworkProvider.class), anyBoolean()); // Verify that only the VCN for the active data SIM was started. verify(mMockDeps) .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any()); verify(mMockDeps, never()) .newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), eq(snapshot), any()); } @Test public void testTelephonyNetworkTrackerCallbackSwitchingActiveDataStartsAndStopsInstances() throws Exception { // Add a record for a non-active SIM mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_1); TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_2, new ArraySet<>(Arrays.asList(TEST_UUID_1, TEST_UUID_2))); // Verify that a new VCN for UUID_2 was started, and the old instance was torn down // immediately verify(mMockDeps) .newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), eq(snapshot), any()); verify(vcn).teardownAsynchronously(); assertEquals(1, mVcnMgmtSvc.getAllVcns().size()); assertFalse(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_1)); assertTrue(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_2)); } @Test public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception { setupActiveSubscription(TEST_UUID_2); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); // Verify teardown after delay mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); verify(mMockPolicyListener).onPolicyChanged(); } @Test public void testTelephonyNetworkTrackerCallbackSwitchToNewSubscriptionImmediatelyTearsDown() throws Exception { setupActiveSubscription(TEST_UUID_2); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); // Simulate switch to different default data subscription that does not have a VCN. triggerSubscriptionTrackerCbAndGetSnapshot( TEST_SUBSCRIPTION_ID, null /* activeDataSubscriptionGroup */, Collections.emptySet(), Collections.emptyMap(), false /* hasCarrierPrivileges */); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); assertEquals(0, mVcnMgmtSvc.getAllVcns().size()); } /** * Tests an intermediate state where carrier privileges are marked as lost before active data * subId changes during a SIM ejection. * *

The expected outcome is that the VCN is torn down after a delay, as opposed to * immediately. */ @Test public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges() throws Exception { setupActiveSubscription(TEST_UUID_2); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); // Simulate privileges lost triggerSubscriptionTrackerCbAndGetSnapshot( TEST_SUBSCRIPTION_ID, TEST_UUID_2, Collections.emptySet(), Collections.emptyMap(), false /* hasCarrierPrivileges */); // Verify teardown after delay mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); } @Test public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances() throws Exception { setupActiveSubscription(TEST_UUID_2); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); // Simulate SIM unloaded triggerSubscriptionTrackerCbAndGetSnapshot( INVALID_SUBSCRIPTION_ID, null /* activeDataSubscriptionGroup */, Collections.emptySet(), Collections.emptyMap(), false /* hasCarrierPrivileges */); // Simulate new SIM loaded right during teardown delay. mTestLooper.moveTimeForward( VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); mTestLooper.dispatchAll(); triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); // Verify that even after the full timeout duration, the VCN instance is not torn down mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); verify(vcn, never()).teardownAsynchronously(); } @Test public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception { setupActiveSubscription(TEST_UUID_2); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); // Simulate SIM unloaded triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new // vcnInstance. mTestLooper.moveTimeForward( VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); mTestLooper.dispatchAll(); mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); // Verify that new instance was different, and the old one was torn down assertTrue(oldInstance != newInstance); verify(oldInstance).teardownAsynchronously(); // Verify that even after the full timeout duration, the new VCN instance is not torn down mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); verify(newInstance, never()).teardownAsynchronously(); } @Test public void testPackageChangeListenerRegistered() throws Exception { verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> { return filter.hasAction(Intent.ACTION_PACKAGE_ADDED) && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED) && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); }), any(), any()); } @Test public void testPackageChangeListener_packageAdded() throws Exception { final BroadcastReceiver receiver = getPackageChangeReceiver(); verify(mMockContext).registerReceiver(any(), argThat(filter -> { return filter.hasAction(Intent.ACTION_PACKAGE_ADDED) && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED) && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); }), any(), any()); receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_ADDED)); verify(mSubscriptionTracker).handleSubscriptionsChanged(); } @Test public void testPackageChangeListener_packageRemoved() throws Exception { final BroadcastReceiver receiver = getPackageChangeReceiver(); verify(mMockContext).registerReceiver(any(), argThat(filter -> { return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); }), any(), any()); receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED)); verify(mSubscriptionTracker).handleSubscriptionsChanged(); } @Test public void testPackageChangeListener_packageDataCleared() throws Exception { triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1)); final Vcn vcn = mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1); final BroadcastReceiver receiver = getPackageChangeReceiver(); assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs()); final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED); intent.setData(Uri.parse("package:" + TEST_PACKAGE_NAME)); intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(TEST_UID)); receiver.onReceive(mMockContext, intent); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @Test public void testPackageChangeListener_packageFullyRemoved() throws Exception { triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1)); final Vcn vcn = mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1); final BroadcastReceiver receiver = getPackageChangeReceiver(); assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs()); final Intent intent = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED); intent.setData(Uri.parse("package:" + TEST_PACKAGE_NAME)); intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(TEST_UID)); receiver.onReceive(mMockContext, intent); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } } @Test public void testSetVcnConfigRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { verify(mMockPolicyListener, never()).onPolicyChanged(); } } @Test public void testSetVcnConfigRequiresCarrierPrivileges() throws Exception { setupMockedCarrierPrivilege(false); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { verify(mMockPolicyListener, never()).onPolicyChanged(); } } @Test public void testSetVcnConfigMismatchedPackages() throws Exception { try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME_2); fail("Expected exception due to mismatched packages in config and method call"); } catch (IllegalArgumentException expected) { verify(mMockPolicyListener, never()).onPolicyChanged(); } } @Test public void testSetVcnConfig() throws Exception { // Use a different UUID to simulate a new 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)); } @Test public void testSetVcnConfigNonActiveSimDoesNotStartVcn() throws Exception { // Use a different UUID to simulate a new 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)); verify(mMockDeps, never()).newVcn(any(), any(), any(), any(), any()); } @Test public void testSetVcnConfigActiveSimTearsDownExistingVcnsImmediately() throws Exception { final Vcn vcn = startAndGetVcnInstance(TEST_UUID_1); // Use a different UUID to simulate a new VCN config. setupActiveSubscription(TEST_UUID_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); verify(mMockDeps, times(2)).newVcn(any(), any(), any(), any(), any()); verify(vcn).teardownAsynchronously(); assertEquals(1, mVcnMgmtSvc.getAllVcns().size()); assertFalse(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_1)); assertTrue(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_2)); } @Test public void testSetVcnConfigTestModeRequiresPermission() throws Exception { doThrow(new SecurityException("Requires MANAGE_TEST_NETWORKS")) .when(mMockContext) .enforceCallingPermission( eq(android.Manifest.permission.MANAGE_TEST_NETWORKS), any()); final VcnConfig vcnConfig = new VcnConfig.Builder(mMockContext) .addGatewayConnectionConfig( VcnGatewayConnectionConfigTest.buildTestConfig()) .setIsTestModeProfile() .build(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME); fail("Expected exception due to using test-mode without permission"); } catch (SecurityException e) { verify(mMockPolicyListener, never()).onPolicyChanged(); } } @Test public void testSetVcnConfigNotifiesStatusCallback() throws Exception { triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); // Use a different UUID to simulate a new VCN config. mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); } @Test public void testClearVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } } @Test public void testClearVcnConfigRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfigRequiresCarrierPrivilegesOrProvisioningPackage() throws Exception { setupMockedCarrierPrivilege(false); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfigMismatchedPackages() throws Exception { try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2); fail("Expected security exception due to mismatched packages"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfig_callerIsProvisioningPackage() throws Exception { // Lose carrier privileges to test that provisioning package is sufficient. setupMockedCarrierPrivilege(false); mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @Test public void testClearVcnConfig_callerIsCarrierPrivileged() throws Exception { setupMockedCarrierPrivilege(true, TEST_PACKAGE_NAME_2); mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2); assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } @Test public void testClearVcnConfigNotifiesStatusCallback() throws Exception { setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); } @Test public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTearsDownVcns() throws Exception { setupActiveSubscription(TEST_UUID_2); // Use a different UUID to simulate a new 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()); assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2)); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); // Verify Vcn is started verify(mMockDeps) .newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), any(), any()); // Verify Vcn is updated if it was previously started 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 mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); verify(vcnInstance).teardownAsynchronously(); } @Test public void testGetConfiguredSubscriptionGroupsRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } } @Test public void testGetConfiguredSubscriptionGroupsMismatchedPackages() throws Exception { doThrow(new SecurityException()) .when(mAppOpsMgr) .checkPackage(TEST_UID, TEST_PACKAGE_NAME_2); try { mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME_2); fail("Expected security exception due to mismatched packages"); } catch (SecurityException expected) { } } @Test public void testGetConfiguredSubscriptionGroups() throws Exception { setupMockedCarrierPrivilege(true, TEST_PACKAGE_NAME_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); mVcnMgmtSvc.setVcnConfig(TEST_UUID_3, TEST_VCN_CONFIG_PKG_2, TEST_PACKAGE_NAME_2); // Assert that if UUIDs 1, 2 and 3 are provisioned, the caller only gets ones that they are // privileged for, or are the provisioning package of. triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1)); final List subGrps = mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME); assertEquals(Arrays.asList(new ParcelUuid[] {TEST_UUID_1, TEST_UUID_2}), subGrps); } @Test public void testAddVcnUnderlyingNetworkPolicyListener() throws Exception { mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); verify(mMockIBinder).linkToDeath(any(), anyInt()); } @Test(expected = SecurityException.class) public void testAddVcnUnderlyingNetworkPolicyListenerInvalidPermission() { doReturn(PackageManager.PERMISSION_DENIED) .when(mMockContext) .checkCallingOrSelfPermission(any()); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } @Test public void testRemoveVcnUnderlyingNetworkPolicyListener() { mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } @Test(expected = SecurityException.class) public void testRemoveVcnUnderlyingNetworkPolicyListenerInvalidPermission() { doReturn(PackageManager.PERMISSION_DENIED) .when(mMockContext) .checkCallingOrSelfPermission(any()); mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } @Test public void testRemoveVcnUnderlyingNetworkPolicyListenerNeverRegistered() { mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } private void verifyMergedNetworkCapabilities( NetworkCapabilities mergedCapabilities, int transportType, boolean isVcnManaged, boolean isRestricted) { assertTrue(mergedCapabilities.hasTransport(transportType)); assertEquals( !isVcnManaged, mergedCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)); assertEquals( !isRestricted, mergedCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)); } private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) { setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive, true /* hasCarrierPrivileges */); } private void setupSubscriptionAndStartVcn( int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) { mVcnMgmtSvc.systemReady(); triggerSubscriptionTrackerCbAndGetSnapshot( subGrp, Collections.singleton(subGrp), Collections.singletonMap(subId, subGrp), hasCarrierPrivileges); final Vcn vcn = startAndGetVcnInstance(subGrp); doReturn(isVcnActive ? VCN_STATUS_CODE_ACTIVE : VCN_STATUS_CODE_SAFE_MODE) .when(vcn) .getStatus(); } private NetworkCapabilities.Builder getNetworkCapabilitiesBuilderForTransport( int subId, int transport) { final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder() .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addTransportType(transport); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { ncBuilder.setSubscriptionIds(Collections.singleton(subId)); } return ncBuilder; } private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport( int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) { setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive); return mVcnMgmtSvc.getUnderlyingNetworkPolicy( getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), TEST_LP_1); } private void checkGetRestrictedTransportsFromCarrierConfig( ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot, Set expectedTransports) { Set result = new VcnManagementService.Dependencies() .getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot); assertEquals(expectedTransports, result); } @Test public void testGetRestrictedTransportsFromCarrierConfig() { final Set restrictedTransports = new ArraySet<>(); restrictedTransports.add(TRANSPORT_CELLULAR); restrictedTransports.add(TRANSPORT_WIFI); PersistableBundle carrierConfigBundle = new PersistableBundle(); carrierConfigBundle.putIntArray( VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, restrictedTransports.stream().mapToInt(i -> i).toArray()); final PersistableBundleWrapper carrierConfig = new PersistableBundleWrapper(carrierConfigBundle); final TelephonySubscriptionSnapshot lastSnapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); checkGetRestrictedTransportsFromCarrierConfig( TEST_UUID_2, lastSnapshot, restrictedTransports); } @Test public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() { final Set restrictedTransports = Collections.singleton(TRANSPORT_WIFI); final PersistableBundleWrapper carrierConfig = new PersistableBundleWrapper(new PersistableBundle()); final TelephonySubscriptionSnapshot lastSnapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); checkGetRestrictedTransportsFromCarrierConfig( TEST_UUID_2, lastSnapshot, restrictedTransports); } @Test public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() { final Set restrictedTransports = Collections.singleton(TRANSPORT_WIFI); final TelephonySubscriptionSnapshot lastSnapshot = mock(TelephonySubscriptionSnapshot.class); checkGetRestrictedTransportsFromCarrierConfig( TEST_UUID_2, lastSnapshot, restrictedTransports); } @Test public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() { // Configure restricted transport in CarrierConfig final Set restrictedTransportInCarrierConfig = Collections.singleton(TRANSPORT_WIFI); PersistableBundle carrierConfigBundle = new PersistableBundle(); carrierConfigBundle.putIntArray( VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, restrictedTransportInCarrierConfig.stream().mapToInt(i -> i).toArray()); final PersistableBundleWrapper carrierConfig = new PersistableBundleWrapper(carrierConfigBundle); final TelephonySubscriptionSnapshot lastSnapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); // Configure restricted transport in VcnConfig final Context mockContext = mock(Context.class); doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName(); final VcnConfig vcnConfig = VcnConfigTest.buildTestConfig( mockContext, Collections.singleton(TRANSPORT_CELLULAR)); // Verifications final Set expectedTransports = new ArraySet<>(); expectedTransports.add(TRANSPORT_CELLULAR); expectedTransports.add(TRANSPORT_WIFI); Set result = new VcnManagementService.Dependencies() .getRestrictedTransports(TEST_UUID_2, lastSnapshot, vcnConfig); assertEquals(expectedTransports, result); } private void checkGetUnderlyingNetworkPolicy( int transportType, boolean isTransportRestricted, boolean isActive, boolean expectVcnManaged, boolean expectRestricted) throws Exception { final Set restrictedTransports = new ArraySet(); if (isTransportRestricted) { restrictedTransports.add(transportType); } doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any(), any()); final VcnUnderlyingNetworkPolicy policy = startVcnAndGetPolicyForTransport( TEST_SUBSCRIPTION_ID, TEST_UUID_2, isActive, transportType); assertFalse(policy.isTeardownRequested()); verifyMergedNetworkCapabilities( policy.getMergedNetworkCapabilities(), transportType, expectVcnManaged, expectRestricted); } @Test public void testGetUnderlyingNetworkPolicy_unrestrictCell() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_CELLULAR, false /* isTransportRestricted */, true /* isActive */, true /* expectVcnManaged */, false /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_unrestrictCellSafeMode() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_CELLULAR, false /* isTransportRestricted */, false /* isActive */, false /* expectVcnManaged */, false /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_restrictCell() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_CELLULAR, true /* isTransportRestricted */, true /* isActive */, true /* expectVcnManaged */, true /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_restrictCellSafeMode() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_CELLULAR, true /* isTransportRestricted */, false /* isActive */, false /* expectVcnManaged */, false /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_unrestrictWifi() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_WIFI, false /* isTransportRestricted */, true /* isActive */, true /* expectVcnManaged */, false /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_unrestrictWifiSafeMode() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_WIFI, false /* isTransportRestricted */, false /* isActive */, false /* expectVcnManaged */, false /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_restrictWifi() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_WIFI, true /* isTransportRestricted */, true /* isActive */, true /* expectVcnManaged */, true /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicy_restrictWifiSafeMode() throws Exception { checkGetUnderlyingNetworkPolicy( TRANSPORT_WIFI, true /* isTransportRestricted */, false /* isActive */, false /* expectVcnManaged */, true /* expectRestricted */); } @Test public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception { doReturn(Collections.singleton(TRANSPORT_WIFI)) .when(mMockDeps) .getRestrictedTransports(any(), any(), any()); setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */); // Get the policy for a cellular network and expect it won't be affected by the wifi // restriction policy final VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy( getNetworkCapabilitiesBuilderForTransport( TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR) .build(), new LinkProperties()); assertFalse(policy.isTeardownRequested()); verifyMergedNetworkCapabilities( policy.getMergedNetworkCapabilities(), TRANSPORT_CELLULAR, true /* expectVcnManaged */, false /* expectRestricted */); } private void setupTrackedNetwork(NetworkCapabilities caps, LinkProperties lp) { mVcnMgmtSvc.systemReady(); final ArgumentCaptor captor = ArgumentCaptor.forClass(NetworkCallback.class); verify(mConnMgr) .registerNetworkCallback( eq(new NetworkRequest.Builder().clearCapabilities().build()), captor.capture()); Network mockNetwork = mock(Network.class, CALLS_REAL_METHODS); captor.getValue().onCapabilitiesChanged(mockNetwork, caps); captor.getValue().onLinkPropertiesChanged(mockNetwork, lp); } @Test public void testGetUnderlyingNetworkPolicyVcnWifi_unrestrictingExistingNetworkRequiresRestart() throws Exception { final NetworkCapabilities existingNetworkCaps = getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) .build(); setupTrackedNetwork(existingNetworkCaps, TEST_LP_1); // Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED // immutable capability final VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy( getNetworkCapabilitiesBuilderForTransport( TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) .build(), TEST_LP_1); assertTrue(policy.isTeardownRequested()); } @Test public void testGetUnderlyingNetworkPolicyVcnWifi_restrictingExistingNetworkRequiresRestart() throws Exception { final NetworkCapabilities existingNetworkCaps = getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) .build(); setupTrackedNetwork(existingNetworkCaps, TEST_LP_1); final VcnUnderlyingNetworkPolicy policy = startVcnAndGetPolicyForTransport( TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI); assertTrue(policy.isTeardownRequested()); } @Test public void testGetUnderlyingNetworkPolicyForRestrictedImsWhenUnrestrictingCell() throws Exception { final NetworkCapabilities existingNetworkCaps = getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_NOT_RESTRICTED) .removeCapability(NET_CAPABILITY_IMS) .build(); setupTrackedNetwork(existingNetworkCaps, TEST_LP_1); final VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy( getNetworkCapabilitiesBuilderForTransport( TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_IMS) .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) .build(), new LinkProperties()); assertFalse(policy.isTeardownRequested()); } @Test public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception { setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */); NetworkCapabilities nc = new NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2)) .build(); VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); assertFalse(policy.isTeardownRequested()); assertEquals(nc, policy.getMergedNetworkCapabilities()); } /** * Checks that networks with similar capabilities do not clobber each other. * *

In previous iterations, the VcnMgmtSvc used capability-matching to check if a network * undergoing policy checks were the same as an existing networks. However, this meant that if * there were newly added capabilities that the VCN did not check, two networks differing only * by that capability would restart each other constantly. */ @Test public void testGetUnderlyingNetworkPolicySimilarNetworks() throws Exception { NetworkCapabilities nc1 = new NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addCapability(NET_CAPABILITY_INTERNET) .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2)) .build(); NetworkCapabilities nc2 = new NetworkCapabilities.Builder(nc1) .addCapability(NET_CAPABILITY_ENTERPRISE) .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) .build(); setupTrackedNetwork(nc1, TEST_LP_1); VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc2, TEST_LP_2); assertFalse(policy.isTeardownRequested()); assertEquals(nc2, policy.getMergedNetworkCapabilities()); } @Test(expected = SecurityException.class) public void testGetUnderlyingNetworkPolicyInvalidPermission() { doReturn(PackageManager.PERMISSION_DENIED) .when(mMockContext) .checkCallingOrSelfPermission(any()); mVcnMgmtSvc.getUnderlyingNetworkPolicy(new NetworkCapabilities(), new LinkProperties()); } @Test public void testSubscriptionSnapshotUpdateNotifiesVcn() { setupActiveSubscription(TEST_UUID_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); final Map vcnInstances = mVcnMgmtSvc.getAllVcns(); final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2); TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_2, Collections.singleton(TEST_UUID_2)); verify(vcnInstance).updateSubscriptionSnapshot(eq(snapshot)); } @Test public void testAddNewVcnUpdatesPolicyListener() throws Exception { setupActiveSubscription(TEST_UUID_2); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); verify(mMockPolicyListener).onPolicyChanged(); } @Test public void testVcnConfigChangeUpdatesPolicyListener() throws Exception { setupActiveSubscription(TEST_UUID_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); final Context mockContext = mock(Context.class); doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName(); final VcnConfig vcnConfig = VcnConfigTest.buildTestConfig( mockContext, Collections.singleton(TRANSPORT_CELLULAR)); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME); verify(mMockPolicyListener).onPolicyChanged(); } @Test public void testRemoveVcnUpdatesPolicyListener() throws Exception { setupActiveSubscription(TEST_UUID_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); verify(mMockPolicyListener).onPolicyChanged(); } @Test public void testVcnSubIdChangeUpdatesPolicyListener() throws Exception { setupActiveSubscription(TEST_UUID_2); startAndGetVcnInstance(TEST_UUID_2); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_2, Collections.singleton(TEST_UUID_2), Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_2)); verify(mMockPolicyListener).onPolicyChanged(); } @Test public void testVcnCarrierConfigChangeUpdatesPolicyListener() throws Exception { setupActiveSubscription(TEST_UUID_2); mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); final TelephonySubscriptionSnapshot snapshot = buildSubscriptionSnapshot( TEST_SUBSCRIPTION_ID, TEST_UUID_2, Collections.singleton(TEST_UUID_2), Collections.emptyMap(), true /* hasCarrierPrivileges */); final PersistableBundleWrapper mockCarrierConfig = mock(PersistableBundleWrapper.class); doReturn(mockCarrierConfig).when(snapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); cb.onNewSnapshot(snapshot); verify(mMockPolicyListener).onPolicyChanged(); } private void triggerVcnSafeMode( @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot, boolean isInSafeMode) throws Exception { verify(mMockDeps) .newVcn( eq(mVcnContext), eq(subGroup), eq(TEST_VCN_CONFIG), eq(snapshot), mVcnCallbackCaptor.capture()); VcnCallback vcnCallback = mVcnCallbackCaptor.getValue(); vcnCallback.onSafeModeStatusChanged(isInSafeMode); } private void verifyVcnSafeModeChangesNotifiesPolicyListeners(boolean enterSafeMode) throws Exception { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_1, Collections.singleton(TEST_UUID_1)); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerVcnSafeMode(TEST_UUID_1, snapshot, enterSafeMode); verify(mMockPolicyListener).onPolicyChanged(); } @Test public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception { verifyVcnSafeModeChangesNotifiesPolicyListeners(true /* enterSafeMode */); } @Test public void testVcnExitingSafeModeNotifiesPolicyListeners() throws Exception { verifyVcnSafeModeChangesNotifiesPolicyListeners(false /* enterSafeMode */); } private void triggerVcnStatusCallbackOnSafeModeStatusChanged( @NonNull ParcelUuid subGroup, @NonNull String pkgName, int uid, boolean hasPermissionsforSubGroup) throws Exception { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( subGroup, Collections.singleton(subGroup)); setupSubscriptionAndStartVcn( TEST_SUBSCRIPTION_ID, subGroup, true /* isActive */, hasPermissionsforSubGroup); doReturn(hasPermissionsforSubGroup) .when(snapshot) .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName)); mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName); triggerVcnSafeMode(subGroup, snapshot, true /* enterSafeMode */); } @Test public void testVcnStatusCallbackOnSafeModeStatusChangedWithCarrierPrivileges() throws Exception { triggerVcnStatusCallbackOnSafeModeStatusChanged( TEST_UUID_1, TEST_PACKAGE_NAME, TEST_UID, true /* hasPermissionsforSubGroup */); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test public void testVcnStatusCallbackOnSafeModeStatusChangedWithoutCarrierPrivileges() throws Exception { triggerVcnStatusCallbackOnSafeModeStatusChanged( TEST_UUID_1, TEST_PACKAGE_NAME, TEST_UID, false /* hasPermissionsforSubGroup */); verify(mMockStatusCallback, never()) .onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test public void testRegisterVcnStatusCallback() throws Exception { mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); Map callbacks = mVcnMgmtSvc.getAllStatusCallbacks(); VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder); assertNotNull(cbInfo); assertEquals(TEST_UUID_1, cbInfo.mSubGroup); assertEquals(mMockStatusCallback, cbInfo.mCallback); assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName); assertEquals(TEST_UID, cbInfo.mUid); verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt()); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); } @Test public void testRegisterVcnStatusCallback_MissingPermission() throws Exception { setupSubscriptionAndStartVcn( TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */, false /* hasCarrierPrivileges */); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED); } @Test public void testRegisterVcnStatusCallback_VcnInactive() throws Exception { setupSubscriptionAndStartVcn( TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */, true /* hasCarrierPrivileges */); // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown // timeout so the VCN goes inactive. final TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_1, Collections.singleton(TEST_UUID_1), Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1), false /* hasCarrierPrivileges */); mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE // when the status callback is registered). Instead, setup permissions for TEST_CB_PACKAGE // so that it's permissioned to receive INACTIVE (instead of NOT_CONFIGURED) without // reactivating the VCN. doReturn(true) .when(snapshot) .packageHasPermissionsForSubscriptionGroup( eq(TEST_UUID_1), eq(TEST_CB_PACKAGE_NAME)); mVcnMgmtSvc.registerVcnStatusCallback( TEST_UUID_1, mMockStatusCallback, TEST_CB_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_INACTIVE); } @Test public void testRegisterVcnStatusCallback_VcnActive() throws Exception { setupSubscriptionAndStartVcn( TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */, true /* hasCarrierPrivileges */); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); } @Test public void testRegisterVcnStatusCallback_VcnSafeMode() throws Exception { setupSubscriptionAndStartVcn( TEST_SUBSCRIPTION_ID, TEST_UUID_1, false /* isActive */, true /* hasCarrierPrivileges */); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE); } @Test(expected = IllegalStateException.class) public void testRegisterVcnStatusCallbackDuplicate() { mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); } @Test public void testUnregisterVcnStatusCallback() { mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); Map callbacks = mVcnMgmtSvc.getAllStatusCallbacks(); VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder); mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback); assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty()); verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt()); } @Test(expected = SecurityException.class) public void testRegisterVcnStatusCallbackInvalidPackage() { doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME); mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); } @Test public void testUnregisterVcnStatusCallbackNeverRegistered() { mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback); assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty()); } }