Add TelephonySubscriptionTracker to VcnMgmtSvc
This changes adds the TelephonySubscriptionTracker to VcnManagementService, ensuring that VCN Instances are only ever started once a relevant Subscription has been loaded in Telephony Bug: 163430955 Test: New VcnManagementService tests added, passing Change-Id: Ia9396d66b41fadc0a5ed7aa66306e6223a2d29b8
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
@@ -43,6 +46,7 @@ import android.util.Slog;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.annotations.VisibleForTesting.Visibility;
|
||||
import com.android.server.vcn.TelephonySubscriptionTracker;
|
||||
import com.android.server.vcn.Vcn;
|
||||
import com.android.server.vcn.VcnContext;
|
||||
import com.android.server.vcn.util.PersistableBundleUtils;
|
||||
@@ -53,6 +57,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
|
||||
@@ -117,6 +122,9 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";
|
||||
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
|
||||
|
||||
/* Binder context for this service */
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final Dependencies mDeps;
|
||||
@@ -124,6 +132,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@NonNull private final Looper mLooper;
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final VcnNetworkProvider mNetworkProvider;
|
||||
@NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
|
||||
@NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
|
||||
@NonNull private final VcnContext mVcnContext;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -134,6 +144,11 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@NonNull
|
||||
private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@NonNull
|
||||
private TelephonySubscriptionSnapshot mLastSnapshot =
|
||||
TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;
|
||||
|
||||
@NonNull private final Object mLock = new Object();
|
||||
|
||||
@NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
|
||||
@@ -146,6 +161,9 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
mLooper = mDeps.getLooper();
|
||||
mHandler = new Handler(mLooper);
|
||||
mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
|
||||
mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
|
||||
mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
|
||||
mContext, mLooper, mTelephonySubscriptionTrackerCb);
|
||||
|
||||
mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
|
||||
mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);
|
||||
@@ -182,7 +200,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
mConfigs.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
// TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed.
|
||||
|
||||
// Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
|
||||
// snapshot, and therefore safe even before telephony subscriptions are loaded.
|
||||
mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -211,6 +232,14 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
return mHandlerThread.getLooper();
|
||||
}
|
||||
|
||||
/** Creates a new VcnInstance using the provided configuration */
|
||||
public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
|
||||
@NonNull Context context,
|
||||
@NonNull Looper looper,
|
||||
@NonNull TelephonySubscriptionTrackerCallback callback) {
|
||||
return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the caller's UID
|
||||
*
|
||||
@@ -255,6 +284,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
public void systemReady() {
|
||||
mContext.getSystemService(ConnectivityManager.class)
|
||||
.registerNetworkProvider(mNetworkProvider);
|
||||
mTelephonySubscriptionTracker.register();
|
||||
}
|
||||
|
||||
private void enforcePrimaryUser() {
|
||||
@@ -301,14 +331,74 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
"Carrier privilege required for subscription group to set VCN Config");
|
||||
}
|
||||
|
||||
private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
|
||||
/**
|
||||
* Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
|
||||
*
|
||||
* <p>Start any unstarted VCN instances
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
|
||||
// Startup VCN instances
|
||||
synchronized (mLock) {
|
||||
mLastSnapshot = snapshot;
|
||||
|
||||
// Start any VCN instances as necessary
|
||||
for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
|
||||
if (snapshot.getActiveSubscriptionGroups().contains(entry.getKey())) {
|
||||
// TODO: Add checks to ensure provisioning app is currently carrier
|
||||
// privileged on this subscription
|
||||
if (!mVcns.containsKey(entry.getKey())) {
|
||||
startVcnLocked(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Cancel any scheduled teardowns for active subscriptions
|
||||
mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule teardown of any VCN instances that have lost carrier privileges (after a
|
||||
// delay)
|
||||
for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
|
||||
if (!snapshot.getActiveSubscriptionGroups().contains(entry.getKey())) {
|
||||
final ParcelUuid uuidToTeardown = entry.getKey();
|
||||
final Vcn instanceToTeardown = entry.getValue();
|
||||
|
||||
mHandler.postDelayed(() -> {
|
||||
synchronized (mLock) {
|
||||
// Guard against case where this is run after a old instance was
|
||||
// torn down, and a new instance was started. Verify to ensure
|
||||
// correct instance is torn down. This could happen as a result of a
|
||||
// Carrier App manually removing/adding a VcnConfig.
|
||||
if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
|
||||
mVcns.remove(uuidToTeardown).teardownAsynchronously();
|
||||
}
|
||||
}
|
||||
}, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
|
||||
Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);
|
||||
|
||||
final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
|
||||
mVcns.put(subscriptionGroup, newInstance);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void startOrUpdateVcnLocked(
|
||||
@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
|
||||
Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup);
|
||||
|
||||
if (mVcns.containsKey(subscriptionGroup)) {
|
||||
mVcns.get(subscriptionGroup).updateConfig(config);
|
||||
} else {
|
||||
final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
|
||||
mVcns.put(subscriptionGroup, newInstance);
|
||||
startVcnLocked(subscriptionGroup, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +411,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
|
||||
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
|
||||
requireNonNull(config, "config was null");
|
||||
Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup);
|
||||
|
||||
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
||||
|
||||
@@ -342,6 +433,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
|
||||
@Override
|
||||
public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
|
||||
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
|
||||
Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
|
||||
|
||||
enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
|
||||
|
||||
|
||||
@@ -233,6 +233,9 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
|
||||
private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
|
||||
private final Set<ParcelUuid> mActiveGroups;
|
||||
|
||||
public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
|
||||
new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptySet());
|
||||
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
TelephonySubscriptionSnapshot(
|
||||
@NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
|
||||
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -25,6 +28,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -44,16 +48,19 @@ import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.VcnManagementService.VcnNetworkProvider;
|
||||
import com.android.server.vcn.TelephonySubscriptionTracker;
|
||||
import com.android.server.vcn.Vcn;
|
||||
import com.android.server.vcn.VcnContext;
|
||||
import com.android.server.vcn.util.PersistableBundleUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Tests for {@link VcnManagementService}. */
|
||||
@@ -66,9 +73,10 @@ public class VcnManagementServiceTest {
|
||||
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 SubscriptionInfo TEST_SUBSCRIPTION_INFO =
|
||||
new SubscriptionInfo(
|
||||
1 /* id */,
|
||||
TEST_SUBSCRIPTION_ID /* id */,
|
||||
"" /* iccId */,
|
||||
0 /* simSlotIndex */,
|
||||
"Carrier" /* displayName */,
|
||||
@@ -99,6 +107,8 @@ public class VcnManagementServiceTest {
|
||||
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 VcnManagementService mVcnMgmtSvc;
|
||||
|
||||
@@ -116,6 +126,12 @@ public class VcnManagementServiceTest {
|
||||
eq(mMockContext),
|
||||
eq(mTestLooper.getLooper()),
|
||||
any(VcnNetworkProvider.class));
|
||||
doReturn(mSubscriptionTracker)
|
||||
.when(mMockDeps)
|
||||
.newTelephonySubscriptionTracker(
|
||||
eq(mMockContext),
|
||||
eq(mTestLooper.getLooper()),
|
||||
any(TelephonySubscriptionTrackerCallback.class));
|
||||
doReturn(mConfigReadWriteHelper)
|
||||
.when(mMockDeps)
|
||||
.newPersistableBundleLockingReadWriteHelper(any());
|
||||
@@ -135,6 +151,9 @@ public class VcnManagementServiceTest {
|
||||
|
||||
setupMockedCarrierPrivilege(true);
|
||||
mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
|
||||
|
||||
// Make sure the profiles are loaded.
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
|
||||
private void setupSystemService(Object service, String name, Class<?> serviceClass) {
|
||||
@@ -157,6 +176,7 @@ public class VcnManagementServiceTest {
|
||||
|
||||
verify(mConnMgr)
|
||||
.registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class));
|
||||
verify(mSubscriptionTracker).register();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,6 +209,94 @@ public class VcnManagementServiceTest {
|
||||
verify(mConfigReadWriteHelper).readFromDisk();
|
||||
}
|
||||
|
||||
private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
|
||||
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
|
||||
doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
|
||||
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
cb.onNewSnapshot(snapshot);
|
||||
}
|
||||
|
||||
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 Vcn startAndGetVcnInstance(ParcelUuid uuid) {
|
||||
mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG);
|
||||
return mVcnMgmtSvc.getAllVcns().get(uuid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
|
||||
triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
|
||||
verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
triggerSubscriptionTrackerCallback(Collections.emptySet());
|
||||
|
||||
// Verify teardown after delay
|
||||
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(vcn).teardownAsynchronously();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
|
||||
throws Exception {
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
// Simulate SIM unloaded
|
||||
triggerSubscriptionTrackerCallback(Collections.emptySet());
|
||||
|
||||
// Simulate new SIM loaded right during teardown delay.
|
||||
mTestLooper.moveTimeForward(
|
||||
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
|
||||
mTestLooper.dispatchAll();
|
||||
triggerSubscriptionTrackerCallback(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 {
|
||||
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
|
||||
final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
|
||||
|
||||
// Simulate SIM unloaded
|
||||
triggerSubscriptionTrackerCallback(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 - 1);
|
||||
mTestLooper.dispatchAll();
|
||||
mVcnMgmtSvc.clearVcnConfig(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 testSetVcnConfigRequiresNonSystemServer() throws Exception {
|
||||
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
|
||||
|
||||
Reference in New Issue
Block a user