diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index d41c0b4fbdb3e..caab15251f58f 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -52,12 +52,17 @@ public final class VcnConfig implements Parcelable { private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; @NonNull private final Set mGatewayConnectionConfigs; + private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile"; + private final boolean mIsTestModeProfile; + private VcnConfig( @NonNull String packageName, - @NonNull Set gatewayConnectionConfigs) { + @NonNull Set gatewayConnectionConfigs, + boolean isTestModeProfile) { mPackageName = packageName; mGatewayConnectionConfigs = Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs)); + mIsTestModeProfile = isTestModeProfile; validate(); } @@ -77,6 +82,7 @@ public final class VcnConfig implements Parcelable { new ArraySet<>( PersistableBundleUtils.toList( gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new)); + mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY); validate(); } @@ -103,6 +109,15 @@ public final class VcnConfig implements Parcelable { return Collections.unmodifiableSet(mGatewayConnectionConfigs); } + /** + * Returns whether or not this VcnConfig is restricted to test networks. + * + * @hide + */ + public boolean isTestModeProfile() { + return mIsTestModeProfile; + } + /** * Serializes this object to a PersistableBundle. * @@ -119,13 +134,14 @@ public final class VcnConfig implements Parcelable { new ArrayList<>(mGatewayConnectionConfigs), VcnGatewayConnectionConfig::toPersistableBundle); result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle); + result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile); return result; } @Override public int hashCode() { - return Objects.hash(mPackageName, mGatewayConnectionConfigs); + return Objects.hash(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile); } @Override @@ -136,7 +152,8 @@ public final class VcnConfig implements Parcelable { final VcnConfig rhs = (VcnConfig) other; return mPackageName.equals(rhs.mPackageName) - && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); + && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs) + && mIsTestModeProfile == rhs.mIsTestModeProfile; } // Parcelable methods @@ -172,6 +189,8 @@ public final class VcnConfig implements Parcelable { @NonNull private final Set mGatewayConnectionConfigs = new ArraySet<>(); + private boolean mIsTestModeProfile = false; + public Builder(@NonNull Context context) { Objects.requireNonNull(context, "context was null"); @@ -206,6 +225,22 @@ public final class VcnConfig implements Parcelable { return this; } + /** + * Restricts this VcnConfig to matching with test networks (only). + * + *

This method is for testing only, and must not be used by apps. Calling {@link + * VcnManager#setVcnConfig(ParcelUuid, VcnConfig)} with a VcnConfig where test-network usage + * is enabled will require the MANAGE_TEST_NETWORKS permission. + * + * @return this {@link Builder} instance, for chaining + * @hide + */ + @NonNull + public Builder setIsTestModeProfile() { + mIsTestModeProfile = true; + return this; + } + /** * Builds and validates the VcnConfig. * @@ -213,7 +248,7 @@ public final class VcnConfig implements Parcelable { */ @NonNull public VcnConfig build() { - return new VcnConfig(mPackageName, mGatewayConnectionConfigs); + return new VcnConfig(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile); } } } diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index be308d01d98a3..2df3e6c7ecd6d 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -15,6 +15,8 @@ */ package android.net.vcn; +import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; + import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.IntDef; @@ -433,6 +435,8 @@ public final class VcnGatewayConnectionConfig { * distinguish between VcnGatewayConnectionConfigs configured on a single {@link * VcnConfig}. This will be used as the identifier in VcnStatusCallback invocations. * @param tunnelConnectionParams the IKE tunnel connection configuration + * @throws IllegalArgumentException if the provided IkeTunnelConnectionParams is not + * configured to support MOBIKE * @see IkeTunnelConnectionParams * @see VcnManager.VcnStatusCallback#onGatewayConnectionError */ @@ -441,6 +445,10 @@ public final class VcnGatewayConnectionConfig { @NonNull IkeTunnelConnectionParams tunnelConnectionParams) { Objects.requireNonNull(gatewayConnectionName, "gatewayConnectionName was null"); Objects.requireNonNull(tunnelConnectionParams, "tunnelConnectionParams was null"); + if (!tunnelConnectionParams.getIkeSessionParams().hasIkeOption(IKE_OPTION_MOBIKE)) { + throw new IllegalArgumentException( + "MOBIKE must be configured for the provided IkeSessionParams"); + } mGatewayConnectionName = gatewayConnectionName; mTunnelConnectionParams = tunnelConnectionParams; diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 1fd637ad20c4f..ddcb82a58534b 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -167,7 +167,6 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final VcnNetworkProvider mNetworkProvider; @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb; @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; - @NonNull private final VcnContext mVcnContext; @NonNull private final BroadcastReceiver mPkgChangeReceiver; @NonNull @@ -212,7 +211,6 @@ public class VcnManagementService extends IVcnManagementService.Stub { mContext, mLooper, mTelephonySubscriptionTrackerCb); mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); - mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); mPkgChangeReceiver = new BroadcastReceiver() { @Override @@ -336,8 +334,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { public VcnContext newVcnContext( @NonNull Context context, @NonNull Looper looper, - @NonNull VcnNetworkProvider vcnNetworkProvider) { - return new VcnContext(context, looper, vcnNetworkProvider); + @NonNull VcnNetworkProvider vcnNetworkProvider, + boolean getIsInTestMode) { + return new VcnContext(context, looper, vcnNetworkProvider, getIsInTestMode); } /** Creates a new Vcn instance using the provided configuration */ @@ -421,6 +420,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Carrier privilege required for subscription group to set VCN Config"); } + private void enforceManageTestNetworksForTestMode(@NonNull VcnConfig vcnConfig) { + if (vcnConfig.isTestModeProfile()) { + mContext.enforceCallingPermission( + android.Manifest.permission.MANAGE_TEST_NETWORKS, + "Test-mode require the MANAGE_TEST_NETWORKS permission"); + } + } + private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback { /** * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker} @@ -544,8 +551,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup); + final VcnContext vcnContext = + mDeps.newVcnContext( + mContext, mLooper, mNetworkProvider, config.isTestModeProfile()); final Vcn newInstance = - mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback); + mDeps.newVcn(vcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback); mVcns.put(subscriptionGroup, newInstance); // Now that a new VCN has started, notify all registered listeners to refresh their @@ -589,6 +599,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { mContext.getSystemService(AppOpsManager.class) .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName()); + enforceManageTestNetworksForTestMode(config); enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName); Binder.withCleanCallingIdentity(() -> { diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index 88180239da677..3c6bb647c5153 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -158,8 +158,15 @@ public class UnderlyingNetworkTracker { * carrier owned networks may be selected, as the request specifies only subIds in the VCN's * subscription group, while the VCN networks are excluded by virtue of not having subIds set on * the VCN-exposed networks. + * + *

If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return + * a NetworkRequest that only matches Test Networks. */ private NetworkRequest getRouteSelectionRequest() { + if (mVcnContext.isInTestMode()) { + return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)); + } + return getBaseNetworkRequestBuilder() .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) .build(); @@ -210,6 +217,16 @@ public class UnderlyingNetworkTracker { .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); } + /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */ + private NetworkRequest getTestNetworkRequest(@NonNull Set subIds) { + return getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .setSubscriptionIds(subIds) + .build(); + } + /** * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. * diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index 7399e56b3a950..d958222ea4070 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -31,14 +31,17 @@ public class VcnContext { @NonNull private final Context mContext; @NonNull private final Looper mLooper; @NonNull private final VcnNetworkProvider mVcnNetworkProvider; + private final boolean mIsInTestMode; public VcnContext( @NonNull Context context, @NonNull Looper looper, - @NonNull VcnNetworkProvider vcnNetworkProvider) { + @NonNull VcnNetworkProvider vcnNetworkProvider, + boolean isInTestMode) { mContext = Objects.requireNonNull(context, "Missing context"); mLooper = Objects.requireNonNull(looper, "Missing looper"); mVcnNetworkProvider = Objects.requireNonNull(vcnNetworkProvider, "Missing networkProvider"); + mIsInTestMode = isInTestMode; } @NonNull @@ -56,6 +59,10 @@ public class VcnContext { return mVcnNetworkProvider; } + public boolean isInTestMode() { + return mIsInTestMode; + } + /** * Verifies that the caller is running on the VcnContext Thread. * diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 9410886c3549e..c59dcf879b1c3 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -16,13 +16,17 @@ package android.net.vcn; +import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.net.NetworkCapabilities; +import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.IkeTunnelConnectionParams; +import android.net.vcn.persistablebundleutils.IkeSessionParamsUtilsTest; import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtilsTest; import androidx.test.filters.SmallTest; @@ -119,6 +123,21 @@ public class VcnGatewayConnectionConfigTest { } } + @Test + public void testBuilderRequiresMobikeEnabled() { + try { + final IkeSessionParams ikeParams = + IkeSessionParamsUtilsTest.createBuilderMinimum() + .removeIkeOption(IKE_OPTION_MOBIKE) + .build(); + final IkeTunnelConnectionParams tunnelParams = + TunnelConnectionParamsUtilsTest.buildTestParams(ikeParams); + new VcnGatewayConnectionConfig.Builder(GATEWAY_CONNECTION_NAME_PREFIX, tunnelParams); + fail("Expected exception due to MOBIKE not enabled"); + } catch (IllegalArgumentException e) { + } + } + @Test public void testBuilderRequiresNonEmptyExposedCaps() { try { diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java index 393787f1a8b87..f3851130c68a1 100644 --- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java @@ -52,8 +52,8 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @SmallTest public class IkeSessionParamsUtilsTest { - // Package private for use in EncryptedTunnelParamsUtilsTest - static IkeSessionParams.Builder createBuilderMinimum() { + // Public for use in VcnGatewayConnectionConfigTest, EncryptedTunnelParamsUtilsTest + public static IkeSessionParams.Builder createBuilderMinimum() { final InetAddress serverAddress = InetAddresses.parseNumericAddress("192.0.2.100"); // TODO: b/185941731 Make sure all valid IKE_OPTIONS are added and validated. @@ -63,6 +63,7 @@ public class IkeSessionParamsUtilsTest { .setLocalIdentification(new IkeFqdnIdentification("client.test.android.net")) .setRemoteIdentification(new IkeFqdnIdentification("server.test.android.net")) .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500) + .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) .setAuthPsk("psk".getBytes()); } diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java index 0c8ad32b0c271..f9dc9eb4d5ae9 100644 --- a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java @@ -18,6 +18,7 @@ package android.net.vcn.persistablebundleutils; import static org.junit.Assert.assertEquals; +import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.IkeTunnelConnectionParams; import androidx.test.filters.SmallTest; @@ -31,9 +32,13 @@ import org.junit.runner.RunWith; public class TunnelConnectionParamsUtilsTest { // Public for use in VcnGatewayConnectionConfigTest public static IkeTunnelConnectionParams buildTestParams() { + return buildTestParams(IkeSessionParamsUtilsTest.createBuilderMinimum().build()); + } + + // Public for use in VcnGatewayConnectionConfigTest + public static IkeTunnelConnectionParams buildTestParams(IkeSessionParams params) { return new IkeTunnelConnectionParams( - IkeSessionParamsUtilsTest.createBuilderMinimum().build(), - TunnelModeChildSessionParamsUtilsTest.createBuilderMinimum().build()); + params, TunnelModeChildSessionParamsUtilsTest.createBuilderMinimum().build()); } @Test diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 9ecd82ff6bcbb..3360d40062a39 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -37,6 +37,7 @@ 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; @@ -66,6 +67,7 @@ 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; @@ -197,7 +199,8 @@ public class VcnManagementServiceTest { .newVcnContext( eq(mMockContext), eq(mTestLooper.getLooper()), - any(VcnNetworkProvider.class)); + any(VcnNetworkProvider.class), + anyBoolean()); doReturn(mSubscriptionTracker) .when(mMockDeps) .newTelephonySubscriptionTracker( @@ -370,6 +373,12 @@ public class VcnManagementServiceTest { public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + verify(mMockDeps) + .newVcnContext( + eq(mMockContext), + eq(mTestLooper.getLooper()), + any(VcnNetworkProvider.class), + anyBoolean()); verify(mMockDeps) .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any()); } @@ -527,6 +536,28 @@ public class VcnManagementServiceTest { verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); } + @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(Collections.singleton(TEST_UUID_2)); diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java index 8289e85dadf9a..6f63c4b2e1533 100644 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -112,8 +113,14 @@ public class UnderlyingNetworkTrackerTest { MockitoAnnotations.initMocks(this); mTestLooper = new TestLooper(); - mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider)); - doNothing().when(mVcnContext).ensureRunningOnLooperThread(); + mVcnContext = + spy( + new VcnContext( + mContext, + mTestLooper.getLooper(), + mVcnNetworkProvider, + false /* isInTestMode */)); + resetVcnContext(); setupSystemService( mContext, @@ -132,6 +139,11 @@ public class UnderlyingNetworkTrackerTest { mNetworkTrackerCb); } + private void resetVcnContext() { + reset(mVcnContext); + doNothing().when(mVcnContext).ensureRunningOnLooperThread(); + } + private static LinkProperties getLinkPropertiesWithName(String iface) { LinkProperties linkProperties = new LinkProperties(); linkProperties.setInterfaceName(iface); @@ -149,7 +161,29 @@ public class UnderlyingNetworkTrackerTest { verifyNetworkRequestsRegistered(INITIAL_SUB_IDS); } + @Test + public void testNetworkCallbacksRegisteredOnStartupForTestMode() { + resetVcnContext(); + when(mVcnContext.isInTestMode()).thenReturn(true); + reset(mConnectivityManager); + + mUnderlyingNetworkTracker = + new UnderlyingNetworkTracker( + mVcnContext, + SUB_GROUP, + mSubscriptionSnapshot, + Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET), + mNetworkTrackerCb); + + verifyNetworkRequestsRegistered(INITIAL_SUB_IDS, true /* expectTestMode */); + } + private void verifyNetworkRequestsRegistered(Set expectedSubIds) { + verifyNetworkRequestsRegistered(expectedSubIds, false /* expectTestMode */); + } + + private void verifyNetworkRequestsRegistered( + Set expectedSubIds, boolean expectTestMode) { verify(mConnectivityManager) .requestBackgroundNetwork( eq(getWifiRequest(expectedSubIds)), @@ -162,10 +196,16 @@ public class UnderlyingNetworkTrackerTest { any(NetworkBringupCallback.class), any()); } + final NetworkRequest expectedRouteSelectionRequest = + expectTestMode + ? getTestNetworkRequest(expectedSubIds) + : getRouteSelectionRequest(expectedSubIds); + verify(mConnectivityManager) .requestBackgroundNetwork( - eq(getRouteSelectionRequest(expectedSubIds)), - any(RouteSelectionCallback.class), any()); + eq(expectedRouteSelectionRequest), + any(RouteSelectionCallback.class), + any()); } @Test @@ -204,6 +244,15 @@ public class UnderlyingNetworkTrackerTest { return getExpectedRequestBase().setSubscriptionIds(netCapsSubIds).build(); } + private NetworkRequest getTestNetworkRequest(Set netCapsSubIds) { + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .setSubscriptionIds(netCapsSubIds) + .build(); + } + private NetworkRequest.Builder getExpectedRequestBase() { final NetworkRequest.Builder builder = new NetworkRequest.Builder()