diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 74e38510770b6..6ed4bbb2b31fb 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -43,6 +43,8 @@ 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.Vcn; +import com.android.server.vcn.VcnContext; import com.android.server.vcn.util.PersistableBundleUtils; import java.io.IOException; @@ -122,11 +124,16 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final Looper mLooper; @NonNull private final Handler mHandler; @NonNull private final VcnNetworkProvider mNetworkProvider; + @NonNull private final VcnContext mVcnContext; @GuardedBy("mLock") @NonNull private final Map mConfigs = new ArrayMap<>(); + @GuardedBy("mLock") + @NonNull + private final Map mVcns = new ArrayMap<>(); + @NonNull private final Object mLock = new Object(); @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper; @@ -141,6 +148,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { mNetworkProvider = new VcnNetworkProvider(mContext, mLooper); mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); + mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); // Run on handler to ensure I/O does not block system server startup mHandler.post(() -> { @@ -225,6 +233,22 @@ public class VcnManagementService extends IVcnManagementService.Stub { newPersistableBundleLockingReadWriteHelper(@NonNull String path) { return new PersistableBundleUtils.LockingReadWriteHelper(path); } + + /** Creates a new VcnContext */ + public VcnContext newVcnContext( + @NonNull Context context, + @NonNull Looper looper, + @NonNull VcnNetworkProvider vcnNetworkProvider) { + return new VcnContext(context, looper, vcnNetworkProvider); + } + + /** Creates a new Vcn instance using the provided configuration */ + public Vcn newVcn( + @NonNull VcnContext vcnContext, + @NonNull ParcelUuid subscriptionGroup, + @NonNull VcnConfig config) { + return new Vcn(vcnContext, subscriptionGroup, config); + } } /** Notifies the VcnManagementService that external dependencies can be set up. */ @@ -277,6 +301,17 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Carrier privilege required for subscription group to set VCN Config"); } + @GuardedBy("mLock") + private void startOrUpdateVcnLocked( + @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + if (mVcns.containsKey(subscriptionGroup)) { + mVcns.get(subscriptionGroup).updateConfig(config); + } else { + final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); + mVcns.put(subscriptionGroup, newInstance); + } + } + /** * Sets a VCN config for a given subscription group. * @@ -289,15 +324,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { enforceCallingUserAndCarrierPrivilege(subscriptionGroup); - synchronized (mLock) { - mConfigs.put(subscriptionGroup, config); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mConfigs.put(subscriptionGroup, config); + startOrUpdateVcnLocked(subscriptionGroup, config); - // Must be done synchronously to ensure that writes do not happen out-of-order. - writeConfigsToDiskLocked(); - } - - // TODO: Clear Binder calling identity - // TODO: Trigger startup as necessary + writeConfigsToDiskLocked(); + } + }); } /** @@ -311,15 +345,17 @@ public class VcnManagementService extends IVcnManagementService.Stub { enforceCallingUserAndCarrierPrivilege(subscriptionGroup); - synchronized (mLock) { - mConfigs.remove(subscriptionGroup); + Binder.withCleanCallingIdentity(() -> { + synchronized (mLock) { + mConfigs.remove(subscriptionGroup); - // Must be done synchronously to ensure that writes do not happen out-of-order. - writeConfigsToDiskLocked(); - } + if (mVcns.containsKey(subscriptionGroup)) { + mVcns.remove(subscriptionGroup).teardownAsynchronously(); + } - // TODO: Clear Binder calling identity - // TODO: Trigger teardown as necessary + writeConfigsToDiskLocked(); + } + }); } @GuardedBy("mLock") @@ -345,6 +381,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + /** Get current configuration list for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Map getAllVcns() { + synchronized (mLock) { + return Collections.unmodifiableMap(mVcns); + } + } + /** * Network provider for VCN networks. * @@ -357,7 +401,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { - // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels. + synchronized (mLock) { + for (Vcn instance : mVcns.values()) { + instance.onNetworkRequested(request, score, providerId); + } + } } } } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index d51d16b1b4df2..493761be62342 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -69,7 +69,7 @@ public class Vcn extends Handler { } /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */ - public void teardown() { + public void teardownAsynchronously() { // TODO: Proxy to handler, and teardown there. } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 1cc953239fed3..25c614ced067b 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -42,6 +43,9 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.VcnManagementService.VcnNetworkProvider; +import com.android.server.vcn.Vcn; +import com.android.server.vcn.VcnContext; import com.android.server.vcn.util.PersistableBundleUtils; import org.junit.Test; @@ -92,10 +96,12 @@ public class VcnManagementServiceTest { private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class); private final TelephonyManager mTelMgr = mock(TelephonyManager.class); private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class); - private final VcnManagementService mVcnMgmtSvc; + private final VcnContext mVcnContext = mock(VcnContext.class); private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper = mock(PersistableBundleUtils.LockingReadWriteHelper.class); + private final VcnManagementService mVcnMgmtSvc; + public VcnManagementServiceTest() throws Exception { setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); @@ -104,10 +110,22 @@ public class VcnManagementServiceTest { doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid(); + doReturn(mVcnContext) + .when(mMockDeps) + .newVcnContext( + eq(mMockContext), + eq(mTestLooper.getLooper()), + any(VcnNetworkProvider.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()); + final PersistableBundle bundle = PersistableBundleUtils.fromMap( TEST_VCN_CONFIG_MAP, @@ -255,4 +273,26 @@ public class VcnManagementServiceTest { assertTrue(mVcnMgmtSvc.getConfigs().isEmpty()); verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } + + @Test + public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception { + // Use a different UUID to simulate a new VCN config. + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG); + 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)); + + // Verify Vcn is updated if it was previously started + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG); + verify(vcnInstance).updateConfig(TEST_VCN_CONFIG); + + // Verify Vcn is stopped if it was already started + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + verify(vcnInstance).teardownAsynchronously(); + } }