diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java index c3dba33ab8811..38b3174abd4c1 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java @@ -353,6 +353,11 @@ public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetwork return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_RCS); } + /** @hide */ + public Map getCapabilitiesMatchCriteria() { + return Collections.unmodifiableMap(new HashMap<>(mCapabilitiesMatchCriteria)); + } + @Override public int hashCode() { return Objects.hash( diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index 2f84fddc72789..2141eba3be50c 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -15,6 +15,7 @@ */ package com.android.server.vcn.routeselection; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -45,6 +46,7 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.VcnContext; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -69,9 +71,23 @@ class NetworkPriorityClassifier { @VisibleForTesting(visibility = Visibility.PRIVATE) static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; - /** Priority for any other networks (including unvalidated, etc) */ + /** + * Priority for networks that VCN can fall back to. + * + *

If none of the network candidates are validated or match any template, VCN will fall back + * to any INTERNET network. + */ @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_ANY = Integer.MAX_VALUE; + static final int PRIORITY_FALLBACK = Integer.MAX_VALUE; + + /** + * Priority for networks that cannot be selected as VCN's underlying networks. + * + *

VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any + * template as the underlying network. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_INVALID = -1; /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */ public static int calculatePriorityClass( @@ -86,12 +102,12 @@ class NetworkPriorityClassifier { if (networkRecord.isBlocked) { logWtf("Network blocked for System Server: " + networkRecord.network); - return PRIORITY_ANY; + return PRIORITY_INVALID; } if (snapshot == null) { logWtf("Got null snapshot"); - return PRIORITY_ANY; + return PRIORITY_INVALID; } int priorityIndex = 0; @@ -108,7 +124,13 @@ class NetworkPriorityClassifier { } priorityIndex++; } - return PRIORITY_ANY; + + final NetworkCapabilities caps = networkRecord.networkCapabilities; + if (caps.hasCapability(NET_CAPABILITY_INTERNET) + || (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST))) { + return PRIORITY_FALLBACK; + } + return PRIORITY_INVALID; } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -297,6 +319,18 @@ class NetworkPriorityClassifier { return false; } + for (Map.Entry entry : + networkPriority.getCapabilitiesMatchCriteria().entrySet()) { + final int cap = entry.getKey(); + final int matchCriteria = entry.getValue(); + + if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) { + return false; + } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) { + return false; + } + } + return true; } diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index 3acbb5e17e13e..6afa795e96fa1 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -16,6 +16,9 @@ package com.android.server.vcn.routeselection; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; import static com.android.server.VcnManagementService.LOCAL_LOG; @@ -32,6 +35,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnCellUnderlyingNetworkTemplate; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; @@ -40,6 +44,7 @@ import android.os.ParcelUuid; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -49,6 +54,7 @@ import com.android.server.vcn.VcnContext; import com.android.server.vcn.util.LogUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -126,6 +132,63 @@ public class UnderlyingNetworkController { registerOrUpdateNetworkRequests(); } + private static class CapabilityMatchCriteria { + public final int capability; + public final int matchCriteria; + + CapabilityMatchCriteria(int capability, int matchCriteria) { + this.capability = capability; + this.matchCriteria = matchCriteria; + } + + @Override + public int hashCode() { + return Objects.hash(capability, matchCriteria); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof CapabilityMatchCriteria)) { + return false; + } + + final CapabilityMatchCriteria rhs = (CapabilityMatchCriteria) other; + return capability == rhs.capability && matchCriteria == rhs.matchCriteria; + } + } + + private static Set> dedupAndGetCapRequirementsForCell( + VcnGatewayConnectionConfig connectionConfig) { + final Set> dedupedCapsMatchSets = new ArraySet<>(); + + for (VcnUnderlyingNetworkTemplate template : + connectionConfig.getVcnUnderlyingNetworkPriorities()) { + if (template instanceof VcnCellUnderlyingNetworkTemplate) { + final Set capsMatchSet = new ArraySet<>(); + + for (Map.Entry entry : + ((VcnCellUnderlyingNetworkTemplate) template) + .getCapabilitiesMatchCriteria() + .entrySet()) { + + final int capability = entry.getKey(); + final int matchCriteria = entry.getValue(); + if (matchCriteria != MATCH_ANY) { + capsMatchSet.add(new CapabilityMatchCriteria(capability, matchCriteria)); + } + } + + dedupedCapsMatchSets.add(capsMatchSet); + } + } + + dedupedCapsMatchSets.add( + Collections.singleton( + new CapabilityMatchCriteria( + NetworkCapabilities.NET_CAPABILITY_INTERNET, MATCH_REQUIRED))); + return dedupedCapsMatchSets; + } + private void registerOrUpdateNetworkRequests() { NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback; NetworkCallback oldWifiCallback = mWifiBringupCallback; @@ -158,11 +221,14 @@ public class UnderlyingNetworkController { getWifiNetworkRequest(), mWifiBringupCallback, mHandler); for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - final NetworkBringupCallback cb = new NetworkBringupCallback(); - mCellBringupCallbacks.add(cb); + for (Set capsMatchCriteria : + dedupAndGetCapRequirementsForCell(mConnectionConfig)) { + final NetworkBringupCallback cb = new NetworkBringupCallback(); + mCellBringupCallbacks.add(cb); - mConnectivityManager.requestBackgroundNetwork( - getCellNetworkRequestForSubId(subId), cb, mHandler); + mConnectivityManager.requestBackgroundNetwork( + getCellNetworkRequestForSubId(subId, capsMatchCriteria), cb, mHandler); + } } } else { mRouteSelectionCallback = null; @@ -214,6 +280,13 @@ public class UnderlyingNetworkController { .build(); } + private NetworkRequest.Builder getBaseWifiNetworkRequestBuilder() { + return getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)); + } + /** * Builds the WiFi bringup request * @@ -224,10 +297,7 @@ public class UnderlyingNetworkController { * but will NEVER bring up a Carrier WiFi network itself. */ private NetworkRequest getWifiNetworkRequest() { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) - .build(); + return getBaseWifiNetworkRequestBuilder().build(); } /** @@ -238,9 +308,7 @@ public class UnderlyingNetworkController { * pace to effectively select a short-lived WiFi offload network. */ private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + return getBaseWifiNetworkRequestBuilder() // Ensure wifi updates signal strengths when crossing this threshold. .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig)) .build(); @@ -254,9 +322,7 @@ public class UnderlyingNetworkController { * pace to effectively select away from a failing WiFi network. */ private NetworkRequest getWifiExitRssiThresholdNetworkRequest() { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + return getBaseWifiNetworkRequestBuilder() // Ensure wifi updates signal strengths when crossing this threshold. .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig)) .build(); @@ -273,11 +339,25 @@ public class UnderlyingNetworkController { *

Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier. */ - private NetworkRequest getCellNetworkRequestForSubId(int subId) { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) - .build(); + private NetworkRequest getCellNetworkRequestForSubId( + int subId, Set capsMatchCriteria) { + final NetworkRequest.Builder nrBuilder = + getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); + + for (CapabilityMatchCriteria capMatchCriteria : capsMatchCriteria) { + final int cap = capMatchCriteria.capability; + final int matchCriteria = capMatchCriteria.matchCriteria; + + if (matchCriteria == MATCH_REQUIRED) { + nrBuilder.addCapability(cap); + } else if (matchCriteria == MATCH_FORBIDDEN) { + nrBuilder.addForbiddenCapability(cap); + } + } + + return nrBuilder.build(); } /** @@ -285,7 +365,6 @@ public class UnderlyingNetworkController { */ private NetworkRequest.Builder getBaseNetworkRequestBuilder() { return new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); @@ -406,7 +485,9 @@ public class UnderlyingNetworkController { mLastSnapshot, mCurrentRecord, mCarrierConfig); - sorted.add(record); + if (record.priorityClass != NetworkPriorityClassifier.PRIORITY_INVALID) { + sorted.add(record); + } } } diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java index 1f6bb2150643d..1569613123239 100644 --- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java @@ -32,7 +32,8 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe private static final Set ALLOWED_PLMN_IDS = new HashSet<>(); private static final Set ALLOWED_CARRIER_IDS = new HashSet<>(); - private static VcnCellUnderlyingNetworkTemplate.Builder getTestNetworkTemplateBuilder() { + // Public for use in UnderlyingNetworkControllerTest + public static VcnCellUnderlyingNetworkTemplate.Builder getTestNetworkTemplateBuilder() { return new VcnCellUnderlyingNetworkTemplate.Builder() .setMetered(MATCH_FORBIDDEN) .setMinUpstreamBandwidthKbps( diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 40408880a2c68..1883c85b5249f 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -100,14 +100,20 @@ public class VcnGatewayConnectionConfigTest { EXPOSED_CAPS); } - // Public for use in VcnGatewayConnectionTest - public static VcnGatewayConnectionConfig buildTestConfig() { + // Public for use in UnderlyingNetworkControllerTest + public static VcnGatewayConnectionConfig buildTestConfig( + List nwTemplates) { final VcnGatewayConnectionConfig.Builder builder = - newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES); + newBuilder().setVcnUnderlyingNetworkPriorities(nwTemplates); return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS); } + // Public for use in VcnGatewayConnectionTest + public static VcnGatewayConnectionConfig buildTestConfig() { + return buildTestConfig(UNDERLYING_NETWORK_TEMPLATES); + } + private static VcnGatewayConnectionConfig.Builder newBuilder() { // Append a unique identifier to the name prefix to guarantee that all created // VcnGatewayConnectionConfigs have a unique name (required by VcnConfig). diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index bb7da5604e169..629e988495ccc 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -16,6 +16,7 @@ package com.android.server.vcn.routeselection; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS; @@ -24,7 +25,8 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS; import static com.android.server.vcn.VcnTestUtils.setupSystemService; -import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule; @@ -64,6 +66,7 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.UUID; @@ -103,6 +106,7 @@ public class NetworkPriorityClassifierTest { private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES = new NetworkCapabilities.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .setSubscriptionIds(Set.of(SUB_ID)) .setNetworkSpecifier(TEL_NETWORK_SPECIFIER) @@ -501,24 +505,72 @@ public class NetworkPriorityClassifierTest { mSubscriptionSnapshot)); } + private void verifyMatchCellWithRequiredCapabilities( + VcnCellUnderlyingNetworkTemplate template, boolean expectMatch) { + assertEquals( + expectMatch, + checkMatchesCellPriorityRule( + mVcnContext, + template, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchCell() { + final VcnCellUnderlyingNetworkTemplate template = + getCellNetworkPriorityBuilder().setInternet(MATCH_REQUIRED).build(); + verifyMatchCellWithRequiredCapabilities(template, true /* expectMatch */); + } + + @Test + public void testMatchCellFail_RequiredCapabilitiesMissing() { + final VcnCellUnderlyingNetworkTemplate template = + getCellNetworkPriorityBuilder().setCbs(MATCH_REQUIRED).build(); + verifyMatchCellWithRequiredCapabilities(template, false /* expectMatch */); + } + + @Test + public void testMatchCellFail_ForbiddenCapabilitiesFound() { + final VcnCellUnderlyingNetworkTemplate template = + getCellNetworkPriorityBuilder().setDun(MATCH_FORBIDDEN).build(); + verifyMatchCellWithRequiredCapabilities(template, false /* expectMatch */); + } + @Test public void testCalculatePriorityClass() throws Exception { assertEquals(2, mCellNetworkRecord.priorityClass); } - @Test - public void testCalculatePriorityClassFailToMatchAny() throws Exception { - final NetworkCapabilities nc = + private void checkCalculatePriorityClassFailToMatchAny( + boolean hasInternet, int expectedPriorityClass) throws Exception { + final List templatesRequireDun = + Collections.singletonList( + new VcnCellUnderlyingNetworkTemplate.Builder() + .setDun(MATCH_REQUIRED) + .build()); + + final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSignalStrength(WIFI_RSSI_LOW) - .setSsid(SSID) - .build(); + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + if (hasInternet) { + ncBuilder.addCapability(NET_CAPABILITY_INTERNET); + } - final UnderlyingNetworkRecord wifiNetworkRecord = - getTestNetworkRecord( - nc, VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES); + final UnderlyingNetworkRecord nonDunNetworkRecord = + getTestNetworkRecord(ncBuilder.build(), templatesRequireDun); - assertEquals(PRIORITY_ANY, wifiNetworkRecord.priorityClass); + assertEquals(expectedPriorityClass, nonDunNetworkRecord.priorityClass); + } + + @Test + public void testCalculatePriorityClassFailToMatchAny_InternetNetwork() throws Exception { + checkCalculatePriorityClassFailToMatchAny(true /* hasInternet */, PRIORITY_FALLBACK); + } + + @Test + public void testCalculatePriorityClassFailToMatchAny_NonInternetNetwork() throws Exception { + checkCalculatePriorityClassFailToMatchAny(false /* hasInternet */, PRIORITY_INVALID); } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java index 73d0a280916a9..2941fdea20bb9 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java @@ -16,6 +16,14 @@ package com.android.server.vcn.routeselection; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_REQUIRED; + import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; @@ -30,6 +38,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.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -44,7 +53,10 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnCellUnderlyingNetworkTemplate; +import android.net.vcn.VcnCellUnderlyingNetworkTemplateTest; import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.ParcelUuid; import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; @@ -66,7 +78,10 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -97,11 +112,39 @@ public class UnderlyingNetworkControllerTest { .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build(); + private static final NetworkCapabilities DUN_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .build(); + + private static final NetworkCapabilities CBS_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .build(); + private static final LinkProperties INITIAL_LINK_PROPERTIES = getLinkPropertiesWithName("initial_iface"); private static final LinkProperties UPDATED_LINK_PROPERTIES = getLinkPropertiesWithName("updated_iface"); + private static final VcnCellUnderlyingNetworkTemplate CELL_TEMPLATE_DUN = + new VcnCellUnderlyingNetworkTemplate.Builder() + .setInternet(MATCH_ANY) + .setDun(MATCH_REQUIRED) + .build(); + + private static final VcnCellUnderlyingNetworkTemplate CELL_TEMPLATE_CBS = + new VcnCellUnderlyingNetworkTemplate.Builder() + .setInternet(MATCH_ANY) + .setCbs(MATCH_REQUIRED) + .build(); + @Mock private Context mContext; @Mock private VcnNetworkProvider mVcnNetworkProvider; @Mock private ConnectivityManager mConnectivityManager; @@ -203,6 +246,107 @@ public class UnderlyingNetworkControllerTest { any()); } + private void verifyRequestBackgroundNetwork( + ConnectivityManager cm, + int expectedSubId, + Set expectedRequiredCaps, + Set expectedForbiddenCaps) { + verify(cm) + .requestBackgroundNetwork( + eq( + getCellRequestForSubId( + expectedSubId, + expectedRequiredCaps, + expectedForbiddenCaps)), + any(NetworkBringupCallback.class), + any()); + } + + @Test + public void testNetworkCallbacksRegisteredOnStartupForNonInternetCapabilities() { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + // Build network templates + final List networkTemplates = new ArrayList(); + + networkTemplates.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplateBuilder() + .setDun(MATCH_REQUIRED) + .setInternet(MATCH_ANY) + .build()); + + networkTemplates.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplateBuilder() + .setMms(MATCH_REQUIRED) + .setCbs(MATCH_FORBIDDEN) + .setInternet(MATCH_ANY) + .build()); + + // Start UnderlyingNetworkController + new UnderlyingNetworkController( + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); + + // Verifications + for (final int subId : INITIAL_SUB_IDS) { + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_INTERNET), + Collections.emptySet()); + verifyRequestBackgroundNetwork( + cm, subId, Collections.singleton(NET_CAPABILITY_DUN), Collections.emptySet()); + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_MMS), + Collections.singleton(NET_CAPABILITY_CBS)); + } + } + + @Test + public void testNetworkCallbacksRegisteredOnStartupWithDedupedtCapabilities() { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + // Build network templates + final List networkTemplates = new ArrayList(); + final VcnCellUnderlyingNetworkTemplate.Builder builder = + new VcnCellUnderlyingNetworkTemplate.Builder() + .setMms(MATCH_REQUIRED) + .setCbs(MATCH_FORBIDDEN) + .setInternet(MATCH_ANY); + + networkTemplates.add(builder.setMetered(MATCH_REQUIRED).build()); + networkTemplates.add(builder.setMetered(MATCH_FORBIDDEN).build()); + + // Start UnderlyingNetworkController + new UnderlyingNetworkController( + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); + + // Verifications + for (final int subId : INITIAL_SUB_IDS) { + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_INTERNET), + Collections.emptySet()); + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_MMS), + Collections.singleton(NET_CAPABILITY_CBS)); + } + } + private void verifyNetworkRequestsRegistered(Set expectedSubIds) { verify(mConnectivityManager) .requestBackgroundNetwork( @@ -212,8 +356,13 @@ public class UnderlyingNetworkControllerTest { for (final int subId : expectedSubIds) { verify(mConnectivityManager) .requestBackgroundNetwork( - eq(getCellRequestForSubId(subId)), - any(NetworkBringupCallback.class), any()); + eq( + getCellRequestForSubId( + subId, + Collections.singleton(NET_CAPABILITY_INTERNET), + Collections.emptySet())), + any(NetworkBringupCallback.class), + any()); } verify(mConnectivityManager) @@ -255,6 +404,7 @@ public class UnderlyingNetworkControllerTest { private NetworkRequest getWifiRequest(Set netCapsSubIds) { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSubscriptionIds(netCapsSubIds) .build(); } @@ -263,6 +413,7 @@ public class UnderlyingNetworkControllerTest { // TODO (b/187991063): Add tests for carrier-config based thresholds return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSubscriptionIds(netCapsSubIds) .setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) .build(); @@ -272,16 +423,27 @@ public class UnderlyingNetworkControllerTest { // TODO (b/187991063): Add tests for carrier-config based thresholds return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSubscriptionIds(netCapsSubIds) .setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) .build(); } - private NetworkRequest getCellRequestForSubId(int subId) { - return getExpectedRequestBase() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) - .build(); + private NetworkRequest getCellRequestForSubId( + int subId, Set requiredCaps, Set forbiddenCaps) { + final NetworkRequest.Builder nqBuilder = + getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); + + for (int cap : requiredCaps) { + nqBuilder.addCapability(cap); + } + for (int cap : forbiddenCaps) { + nqBuilder.addForbiddenCapability(cap); + } + + return nqBuilder.build(); } private NetworkRequest getRouteSelectionRequest(Set netCapsSubIds) { @@ -303,7 +465,6 @@ public class UnderlyingNetworkControllerTest { private NetworkRequest.Builder getExpectedRequestBase() { final NetworkRequest.Builder builder = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); @@ -506,13 +667,7 @@ public class UnderlyingNetworkControllerTest { cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */); - UnderlyingNetworkRecord expectedRecord = - getTestNetworkRecord( - mNetwork, - buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), - INITIAL_LINK_PROPERTIES, - true /* isBlocked */); - verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); + verifyOnSelectedUnderlyingNetworkChanged(null); } @Test @@ -548,5 +703,132 @@ public class UnderlyingNetworkControllerTest { verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); } - // TODO (b/187991063): Add tests for network prioritization + private UnderlyingNetworkListener setupControllerAndGetNetworkListener( + List networkTemplates) { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + new UnderlyingNetworkController( + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); + + verify(cm) + .registerNetworkCallback( + eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), + mUnderlyingNetworkListenerCaptor.capture(), + any()); + + return mUnderlyingNetworkListenerCaptor.getValue(); + } + + private UnderlyingNetworkRecord bringupNetworkAndGetRecord( + UnderlyingNetworkListener cb, + NetworkCapabilities requestNetworkCaps, + List underlyingNetworkTemplates, + UnderlyingNetworkRecord currentlySelected) { + final Network network = mock(Network.class); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(requestNetworkCaps, INITIAL_SUB_IDS); + + cb.onAvailable(network); + cb.onCapabilitiesChanged(network, responseNetworkCaps); + cb.onLinkPropertiesChanged(network, INITIAL_LINK_PROPERTIES); + cb.onBlockedStatusChanged(network, false /* isFalse */); + return new UnderlyingNetworkRecord( + network, + responseNetworkCaps, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */, + mVcnContext, + underlyingNetworkTemplates, + SUB_GROUP, + mSubscriptionSnapshot, + currentlySelected, + null /* carrierConfig */); + } + + @Test + public void testSelectMorePreferredNetwork() { + final List networkTemplates = new ArrayList(); + networkTemplates.add(CELL_TEMPLATE_DUN); + networkTemplates.add(CELL_TEMPLATE_CBS); + + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + // Bring up CBS network + final UnderlyingNetworkRecord cbsNetworkRecord = + bringupNetworkAndGetRecord( + cb, + CBS_NETWORK_CAPABILITIES, + networkTemplates, + null /* currentlySelected */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord)); + + // Bring up DUN network + final UnderlyingNetworkRecord dunNetworkRecord = + bringupNetworkAndGetRecord( + cb, DUN_NETWORK_CAPABILITIES, networkTemplates, cbsNetworkRecord); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord)); + } + + @Test + public void testNeverSelectLessPreferredNetwork() { + final List networkTemplates = new ArrayList(); + networkTemplates.add(CELL_TEMPLATE_DUN); + networkTemplates.add(CELL_TEMPLATE_CBS); + + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + // Bring up DUN network + final UnderlyingNetworkRecord dunNetworkRecord = + bringupNetworkAndGetRecord( + cb, + DUN_NETWORK_CAPABILITIES, + networkTemplates, + null /* currentlySelected */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord)); + + // Bring up CBS network + final UnderlyingNetworkRecord cbsNetworkRecord = + bringupNetworkAndGetRecord( + cb, CBS_NETWORK_CAPABILITIES, networkTemplates, dunNetworkRecord); + verify(mNetworkControllerCb, never()) + .onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord)); + } + + @Test + public void testFailtoMatchTemplateAndFallBackToInternetNetwork() { + final List networkTemplates = new ArrayList(); + + networkTemplates.add( + new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build()); + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + // Bring up an Internet network without DUN capability + final UnderlyingNetworkRecord networkRecord = + bringupNetworkAndGetRecord( + cb, + INITIAL_NETWORK_CAPABILITIES, + networkTemplates, + null /* currentlySelected */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(networkRecord)); + } + + @Test + public void testFailtoMatchTemplateAndNeverFallBackToNonInternetNetwork() { + final List networkTemplates = new ArrayList(); + + networkTemplates.add( + new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build()); + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + bringupNetworkAndGetRecord( + cb, CBS_NETWORK_CAPABILITIES, networkTemplates, null /* currentlySelected */); + + verify(mNetworkControllerCb, never()) + .onSelectedUnderlyingNetworkChanged(any(UnderlyingNetworkRecord.class)); + } }