blob: 7c7dc4d79e9ac8ddd98906eb6df0cdd9356ac630 [file] [log] [blame]
/*
* 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_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_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.TelephonyNetworkSpecifier;
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 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 TEST_PACKAGE_NAME =
VcnManagementServiceTest.class.getPackage().getName();
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 VcnConfig TEST_VCN_CONFIG;
private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
static {
final Context mockConfigContext = mock(Context.class);
doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName();
TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext);
}
private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
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 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<VcnCallback> 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 {
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(mMockContext, 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());
}
private void setupMockedCarrierPrivilege(boolean isPrivileged) {
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(TEST_PACKAGE_NAME));
}
@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<ParcelUuid> activeSubscriptionGroups) {
return triggerSubscriptionTrackerCbAndGetSnapshot(
activeDataSubGrp, activeSubscriptionGroups, Collections.emptyMap());
}
private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
ParcelUuid activeDataSubGrp,
Set<ParcelUuid> activeSubscriptionGroups,
Map<Integer, ParcelUuid> subIdToGroupMap) {
return triggerSubscriptionTrackerCbAndGetSnapshot(
activeDataSubGrp,
activeSubscriptionGroups,
subIdToGroupMap,
true /* hasCarrierPrivileges */);
}
private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
ParcelUuid activeDataSubGrp,
Set<ParcelUuid> activeSubscriptionGroups,
Map<Integer, ParcelUuid> subIdToGroupMap,
boolean hasCarrierPrivileges) {
return triggerSubscriptionTrackerCbAndGetSnapshot(
TEST_SUBSCRIPTION_ID,
activeDataSubGrp,
activeSubscriptionGroups,
subIdToGroupMap,
hasCarrierPrivileges);
}
private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
int activeDataSubId,
ParcelUuid activeDataSubGrp,
Set<ParcelUuid> activeSubscriptionGroups,
Map<Integer, ParcelUuid> 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<ParcelUuid> activeSubscriptionGroups,
Map<Integer, ParcelUuid> 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<String> 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<Integer> subIds = new ArraySet<>();
for (Entry<Integer, ParcelUuid> 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<TelephonySubscriptionTrackerCallback> captor =
ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class);
verify(mMockDeps)
.newTelephonySubscriptionTracker(
eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture());
return captor.getValue();
}
private BroadcastReceiver getPackageChangeReceiver() {
final ArgumentCaptor<BroadcastReceiver> captor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext).registerReceiver(captor.capture(), any(), 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.
*
* <p>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)
&& filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
}), any(), any());
receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED));
verify(mSubscriptionTracker).handleSubscriptionsChanged();
}
@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, "IncorrectPackage");
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 testClearVcnConfigRequiresCarrierPrivileges() throws Exception {
setupMockedCarrierPrivilege(false);
try {
mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected security exception for missing carrier privileges");
} catch (SecurityException expected) {
}
}
@Test
public void testClearVcnConfigMismatchedPackages() throws Exception {
try {
mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage");
fail("Expected security exception due to mismatched packages");
} catch (SecurityException expected) {
}
}
@Test
public void testClearVcnConfig() throws Exception {
mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
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<ParcelUuid, Vcn> 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 {
final String badPackage = "IncorrectPackage";
doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, badPackage);
try {
mVcnMgmtSvc.getConfiguredSubscriptionGroups(badPackage);
fail("Expected security exception due to mismatched packages");
} catch (SecurityException expected) {
}
}
@Test
public void testGetConfiguredSubscriptionGroups() throws Exception {
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
// Assert that if both UUID 1 and 2 are provisioned, the caller only gets ones that they are
// privileged for.
triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1));
final List<ParcelUuid> subGrps =
mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME);
assertEquals(Collections.singletonList(TEST_UUID_1), 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(),
new LinkProperties());
}
@Test
public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR);
assertFalse(policy.isTeardownRequested());
verifyMergedNetworkCapabilities(
policy.getMergedNetworkCapabilities(),
TRANSPORT_CELLULAR,
true /* isVcnManaged */,
false /* isRestricted */);
}
@Test
public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception {
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
TEST_SUBSCRIPTION_ID,
TEST_UUID_2,
false /* isActive */,
TRANSPORT_CELLULAR);
assertFalse(policy.isTeardownRequested());
verifyMergedNetworkCapabilities(
policy.getMergedNetworkCapabilities(),
NetworkCapabilities.TRANSPORT_CELLULAR,
false /* isVcnManaged */,
false /* isRestricted */);
}
@Test
public void testGetUnderlyingNetworkPolicyWifi() throws Exception {
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI);
assertFalse(policy.isTeardownRequested());
verifyMergedNetworkCapabilities(
policy.getMergedNetworkCapabilities(),
NetworkCapabilities.TRANSPORT_WIFI,
true /* isVcnManaged */,
true /* isRestricted */);
}
@Test
public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception {
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI);
assertFalse(policy.isTeardownRequested());
verifyMergedNetworkCapabilities(
policy.getMergedNetworkCapabilities(),
NetworkCapabilities.TRANSPORT_WIFI,
false /* isVcnManaged */,
true /* isRestricted */);
}
private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
mVcnMgmtSvc.systemReady();
final ArgumentCaptor<NetworkCallback> captor =
ArgumentCaptor.forClass(NetworkCallback.class);
verify(mConnMgr)
.registerNetworkCallback(
eq(new NetworkRequest.Builder().clearCapabilities().build()),
captor.capture());
captor.getValue().onCapabilitiesChanged(mock(Network.class, CALLS_REAL_METHODS), caps);
}
@Test
public void testGetUnderlyingNetworkPolicyVcnWifi_unrestrictingExistingNetworkRequiresRestart()
throws Exception {
final NetworkCapabilities existingNetworkCaps =
getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build();
setupTrackedCarrierWifiNetwork(existingNetworkCaps);
// 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(),
new LinkProperties());
assertTrue(policy.isTeardownRequested());
}
@Test
public void testGetUnderlyingNetworkPolicyVcnWifi_restrictingExistingNetworkRequiresRestart()
throws Exception {
final NetworkCapabilities existingNetworkCaps =
getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.build();
setupTrackedCarrierWifiNetwork(existingNetworkCaps);
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI);
assertTrue(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)
.setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
.build();
VcnUnderlyingNetworkPolicy policy =
mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
assertFalse(policy.isTeardownRequested());
assertEquals(nc, 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<ParcelUuid, Vcn> 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 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();
}
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<IBinder, VcnStatusCallbackInfo> 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<IBinder, VcnStatusCallbackInfo> 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());
}
}