diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java index 6b33e4f45c422..50a6bfc065d75 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java @@ -29,6 +29,7 @@ import android.telephony.TelephonyManager; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.util.PersistableBundleUtils; import java.util.ArrayList; @@ -200,6 +201,15 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork && mRequireOpportunistic == rhs.mRequireOpportunistic; } + /** @hide */ + @Override + void dumpTransportSpecificFields(IndentingPrintWriter pw) { + pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString()); + pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString()); + pw.println("mAllowRoaming: " + mAllowRoaming); + pw.println("mRequireOpportunistic: " + mRequireOpportunistic); + } + /** This class is used to incrementally build WifiNetworkPriority objects. */ public static class Builder extends VcnUnderlyingNetworkPriority.Builder { @NonNull private final Set mAllowedNetworkPlmnIds = new ArraySet<>(); diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index de4ada2dbc26f..31e38c0b75749 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -160,7 +160,9 @@ public final class VcnGatewayConnectionConfig { TimeUnit.MINUTES.toMillis(15) }; - private static final LinkedHashSet + /** @hide */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static final LinkedHashSet DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>(); static { diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java index 82f6ae72b43c9..551f75772b9ac 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java @@ -21,8 +21,10 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.PersistableBundle; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -37,10 +39,17 @@ public abstract class VcnUnderlyingNetworkPriority { /** @hide */ protected static final int NETWORK_PRIORITY_TYPE_CELL = 2; - /** Denotes that network quality needs to be OK */ - public static final int NETWORK_QUALITY_OK = 10000; /** Denotes that any network quality is acceptable */ - public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE; + public static final int NETWORK_QUALITY_ANY = 0; + /** Denotes that network quality needs to be OK */ + public static final int NETWORK_QUALITY_OK = 100000; + + private static final SparseArray NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>(); + + static { + NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY"); + NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK"); + } /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -125,6 +134,28 @@ public abstract class VcnUnderlyingNetworkPriority { && mAllowMetered == rhs.mAllowMetered; } + /** @hide */ + abstract void dumpTransportSpecificFields(IndentingPrintWriter pw); + + /** + * Dumps the state of this record for logging and debugging purposes. + * + * @hide + */ + public void dump(IndentingPrintWriter pw) { + pw.println(this.getClass().getSimpleName() + ":"); + pw.increaseIndent(); + + pw.println( + "mNetworkQuality: " + + NETWORK_QUALITY_TO_STRING_MAP.get( + mNetworkQuality, "Invalid value " + mNetworkQuality)); + pw.println("mAllowMetered: " + mAllowMetered); + dumpTransportSpecificFields(pw); + + pw.decreaseIndent(); + } + /** Retrieve the required network quality. */ @NetworkQuality public int getNetworkQuality() { diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java index fc7e7e2c4e41a..2ba9169277054 100644 --- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import java.util.Objects; @@ -81,6 +82,12 @@ public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetwork return mSsid == rhs.mSsid; } + /** @hide */ + @Override + void dumpTransportSpecificFields(IndentingPrintWriter pw) { + pw.println("mSsid: " + mSsid); + } + /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */ @Nullable public String getSsid() { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index be13168815d9f..9a216f8c289f4 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -686,6 +686,7 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkController = mDeps.newUnderlyingNetworkController( mVcnContext, + mConnectionConfig, subscriptionGroup, mLastSnapshot, mUnderlyingNetworkControllerCallback); @@ -2364,11 +2365,12 @@ public class VcnGatewayConnection extends StateMachine { /** Builds a new UnderlyingNetworkController. */ public UnderlyingNetworkController newUnderlyingNetworkController( VcnContext vcnContext, + VcnGatewayConnectionConfig connectionConfig, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkControllerCallback callback) { return new UnderlyingNetworkController( - vcnContext, subscriptionGroup, snapshot, callback); + vcnContext, connectionConfig, subscriptionGroup, snapshot, callback); } /** Builds a new IkeSession. */ 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 bea8ae932a9d3..7b26fe0370c90 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -15,25 +15,36 @@ */ package com.android.server.vcn.routeselection; +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; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY; +import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; import static com.android.server.VcnManagementService.LOCAL_LOG; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnCellUnderlyingNetworkPriority; import android.net.vcn.VcnManager; +import android.net.vcn.VcnUnderlyingNetworkPriority; +import android.net.vcn.VcnWifiUnderlyingNetworkPriority; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; +import java.util.LinkedHashSet; import java.util.Set; /** @hide */ @@ -56,52 +67,20 @@ class NetworkPriorityClassifier { */ @VisibleForTesting(visibility = Visibility.PRIVATE) static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; - /** Priority for any cellular network for which the subscription is listed as opportunistic */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0; - /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_WIFI_IN_USE = 1; - /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_WIFI_PROSPECTIVE = 2; - /** Priority for any standard macro cellular network */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_MACRO_CELLULAR = 3; + /** Priority for any other networks (including unvalidated, etc) */ @VisibleForTesting(visibility = Visibility.PRIVATE) static final int PRIORITY_ANY = Integer.MAX_VALUE; - private static final SparseArray PRIORITY_TO_STRING_MAP = new SparseArray<>(); - - static { - PRIORITY_TO_STRING_MAP.put( - PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY"); - } - - /** - * Gives networks a priority class, based on the following priorities: - * - *
    - *
  1. Opportunistic cellular - *
  2. Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT - *
  3. Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT - *
  4. Macro cellular - *
  5. Any others - *
- */ - static int calculatePriorityClass( + /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */ + public static int calculatePriorityClass( + VcnContext vcnContext, UnderlyingNetworkRecord networkRecord, + LinkedHashSet underlyingNetworkPriorities, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig) { - final NetworkCapabilities caps = networkRecord.networkCapabilities; - // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED if (networkRecord.isBlocked) { @@ -109,8 +88,167 @@ class NetworkPriorityClassifier { return PRIORITY_ANY; } - if (caps.hasTransport(TRANSPORT_CELLULAR) - && isOpportunistic(snapshot, caps.getSubscriptionIds())) { + if (snapshot == null) { + logWtf("Got null snapshot"); + return PRIORITY_ANY; + } + + int priorityIndex = 0; + for (VcnUnderlyingNetworkPriority nwPriority : underlyingNetworkPriorities) { + if (checkMatchesPriorityRule( + vcnContext, + nwPriority, + networkRecord, + subscriptionGroup, + snapshot, + currentlySelected, + carrierConfig)) { + return priorityIndex; + } + priorityIndex++; + } + return PRIORITY_ANY; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static boolean checkMatchesPriorityRule( + VcnContext vcnContext, + VcnUnderlyingNetworkPriority networkPriority, + UnderlyingNetworkRecord networkRecord, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + // TODO: Check Network Quality reported by metric monitors/probers. + + final NetworkCapabilities caps = networkRecord.networkCapabilities; + if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) { + return false; + } + + if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) { + return true; + } + + if (networkPriority instanceof VcnWifiUnderlyingNetworkPriority) { + return checkMatchesWifiPriorityRule( + (VcnWifiUnderlyingNetworkPriority) networkPriority, + networkRecord, + currentlySelected, + carrierConfig); + } + + if (networkPriority instanceof VcnCellUnderlyingNetworkPriority) { + return checkMatchesCellPriorityRule( + vcnContext, + (VcnCellUnderlyingNetworkPriority) networkPriority, + networkRecord, + subscriptionGroup, + snapshot); + } + + logWtf( + "Got unknown VcnUnderlyingNetworkPriority class: " + + networkPriority.getClass().getSimpleName()); + return false; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static boolean checkMatchesWifiPriorityRule( + VcnWifiUnderlyingNetworkPriority networkPriority, + UnderlyingNetworkRecord networkRecord, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + final NetworkCapabilities caps = networkRecord.networkCapabilities; + + if (!caps.hasTransport(TRANSPORT_WIFI)) { + return false; + } + + // TODO: Move the Network Quality check to the network metric monitor framework. + if (networkPriority.getNetworkQuality() + > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) { + return false; + } + + if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) { + return false; + } + + return true; + } + + private static int getWifiQuality( + UnderlyingNetworkRecord networkRecord, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + final NetworkCapabilities caps = networkRecord.networkCapabilities; + final boolean isSelectedNetwork = + currentlySelected != null + && networkRecord.network.equals(currentlySelected.network); + + if (isSelectedNetwork + && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) { + return NETWORK_QUALITY_OK; + } + + if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { + return NETWORK_QUALITY_OK; + } + + return NETWORK_QUALITY_ANY; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static boolean checkMatchesCellPriorityRule( + VcnContext vcnContext, + VcnCellUnderlyingNetworkPriority networkPriority, + UnderlyingNetworkRecord networkRecord, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot) { + final NetworkCapabilities caps = networkRecord.networkCapabilities; + + if (!caps.hasTransport(TRANSPORT_CELLULAR)) { + return false; + } + + final TelephonyNetworkSpecifier telephonyNetworkSpecifier = + ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier()); + if (telephonyNetworkSpecifier == null) { + logWtf("Got null NetworkSpecifier"); + return false; + } + + final int subId = telephonyNetworkSpecifier.getSubscriptionId(); + final TelephonyManager subIdSpecificTelephonyMgr = + vcnContext + .getContext() + .getSystemService(TelephonyManager.class) + .createForSubscriptionId(subId); + + if (!networkPriority.getAllowedPlmnIds().isEmpty()) { + final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator(); + if (!networkPriority.getAllowedPlmnIds().contains(plmnId)) { + return false; + } + } + + if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) { + final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId(); + if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) { + return false; + } + } + + if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) { + return false; + } + + if (networkPriority.requireOpportunistic()) { + if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) { + return false; + } + // If this carrier is the active data provider, ensure that opportunistic is only // ever prioritized if it is also the active data subscription. This ensures that // if an opportunistic subscription is still in the process of being switched to, @@ -121,36 +259,15 @@ class NetworkPriorityClassifier { // Allow the following two cases: // 1. Active subId is NOT in the group that this VCN is supporting // 2. This opportunistic subscription is for the active subId - if (!snapshot.getAllSubIdsInGroup(subscriptionGroup) + if (snapshot.getAllSubIdsInGroup(subscriptionGroup) .contains(SubscriptionManager.getActiveDataSubscriptionId()) - || caps.getSubscriptionIds() + && !caps.getSubscriptionIds() .contains(SubscriptionManager.getActiveDataSubscriptionId())) { - return PRIORITY_OPPORTUNISTIC_CELLULAR; + return false; } } - if (caps.hasTransport(TRANSPORT_WIFI)) { - if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig) - && currentlySelected != null - && networkRecord.network.equals(currentlySelected.network)) { - return PRIORITY_WIFI_IN_USE; - } - - if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { - return PRIORITY_WIFI_PROSPECTIVE; - } - } - - // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might - // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be - // the case if the Default Data SubId does not support certain services (eg voice - // calling) - if (caps.hasTransport(TRANSPORT_CELLULAR) - && !isOpportunistic(snapshot, caps.getSubscriptionIds())) { - return PRIORITY_MACRO_CELLULAR; - } - - return PRIORITY_ANY; + return true; } static boolean isOpportunistic( @@ -185,10 +302,6 @@ class NetworkPriorityClassifier { return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; } - static String priorityClassToString(int priorityClass) { - return PRIORITY_TO_STRING_MAP.get(priorityClass, "unknown"); - } - private static void logWtf(String msg) { Slog.wtf(TAG, msg); LOCAL_LOG.log(TAG + " WTF: " + msg); 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 071c7a683cbfc..cd124ee6a9fa0 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -32,6 +32,8 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnUnderlyingNetworkPriority; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; @@ -68,6 +70,7 @@ public class UnderlyingNetworkController { @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName(); @NonNull private final VcnContext mVcnContext; + @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final UnderlyingNetworkControllerCallback mCb; @NonNull private final Dependencies mDeps; @@ -91,24 +94,22 @@ public class UnderlyingNetworkController { public UnderlyingNetworkController( @NonNull VcnContext vcnContext, + @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkControllerCallback cb) { - this( - vcnContext, - subscriptionGroup, - snapshot, - cb, - new Dependencies()); + this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies()); } private UnderlyingNetworkController( @NonNull VcnContext vcnContext, + @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkControllerCallback cb, @NonNull Dependencies deps) { mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); + mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig"); mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); mCb = Objects.requireNonNull(cb, "Missing cb"); @@ -399,6 +400,8 @@ public class UnderlyingNetworkController { TreeSet sorted = new TreeSet<>( UnderlyingNetworkRecord.getComparator( + mVcnContext, + mConnectionConfig.getVcnUnderlyingNetworkPriorities(), mSubscriptionGroup, mLastSnapshot, mCurrentRecord, @@ -495,12 +498,31 @@ public class UnderlyingNetworkController { pw.println( "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network)); + pw.println("VcnUnderlyingNetworkPriority list:"); + pw.increaseIndent(); + int index = 0; + for (VcnUnderlyingNetworkPriority priority : + mConnectionConfig.getVcnUnderlyingNetworkPriorities()) { + pw.println("Priority index: " + index); + priority.dump(pw); + index++; + } + pw.decreaseIndent(); + pw.println(); + pw.println("Underlying networks:"); pw.increaseIndent(); if (mRouteSelectionCallback != null) { for (UnderlyingNetworkRecord record : mRouteSelectionCallback.getSortedUnderlyingNetworks()) { - record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig); + record.dump( + mVcnContext, + pw, + mConnectionConfig.getVcnUnderlyingNetworkPriorities(), + mSubscriptionGroup, + mLastSnapshot, + mCurrentRecord, + mCarrierConfig); } } pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java index 65c69dedcb28e..27ba854ab1973 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.vcn.VcnUnderlyingNetworkPriority; import android.os.ParcelUuid; import android.os.PersistableBundle; @@ -28,8 +29,10 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.Objects; /** @@ -73,22 +76,64 @@ public class UnderlyingNetworkRecord { } static Comparator getComparator( + VcnContext vcnContext, + LinkedHashSet underlyingNetworkPriorities, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig) { return (left, right) -> { - return Integer.compare( + final int leftIndex = NetworkPriorityClassifier.calculatePriorityClass( - left, subscriptionGroup, snapshot, currentlySelected, carrierConfig), + vcnContext, + left, + underlyingNetworkPriorities, + subscriptionGroup, + snapshot, + currentlySelected, + carrierConfig); + final int rightIndex = NetworkPriorityClassifier.calculatePriorityClass( - right, subscriptionGroup, snapshot, currentlySelected, carrierConfig)); + vcnContext, + right, + underlyingNetworkPriorities, + subscriptionGroup, + snapshot, + currentlySelected, + carrierConfig); + + // In the case of networks in the same priority class, prioritize based on other + // criteria (eg. actively selected network, link metrics, etc) + if (leftIndex == rightIndex) { + // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord + // fall into the same priority class. + if (isSelected(left, currentlySelected)) { + return -1; + } + if (isSelected(left, currentlySelected)) { + return 1; + } + } + return Integer.compare(leftIndex, rightIndex); }; } + private static boolean isSelected( + UnderlyingNetworkRecord recordToCheck, UnderlyingNetworkRecord currentlySelected) { + if (currentlySelected == null) { + return false; + } + if (currentlySelected.network == recordToCheck.network) { + return true; + } + return false; + } + /** Dumps the state of this record for logging and debugging purposes. */ void dump( + VcnContext vcnContext, IndentingPrintWriter pw, + LinkedHashSet underlyingNetworkPriorities, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -96,15 +141,17 @@ public class UnderlyingNetworkRecord { pw.println("UnderlyingNetworkRecord:"); pw.increaseIndent(); - final int priorityClass = + final int priorityIndex = NetworkPriorityClassifier.calculatePriorityClass( - this, subscriptionGroup, snapshot, currentlySelected, carrierConfig); - pw.println( - "Priority class: " - + NetworkPriorityClassifier.priorityClassToString(priorityClass) - + " (" - + priorityClass - + ")"); + vcnContext, + this, + underlyingNetworkPriorities, + subscriptionGroup, + snapshot, + currentlySelected, + carrierConfig); + + pw.println("Priority index:" + priorityIndex); pw.println("mNetwork: " + network); pw.println("mNetworkCapabilities: " + networkCapabilities); pw.println("mLinkProperties: " + linkProperties); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 8a0af2dff8c84..5628321b59757 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -179,7 +179,7 @@ public class VcnGatewayConnectionTestBase { doReturn(mUnderlyingNetworkController) .when(mDeps) - .newUnderlyingNetworkController(any(), any(), any(), any()); + .newUnderlyingNetworkController(any(), any(), any(), any(), any()); doReturn(mWakeLock) .when(mDeps) .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java new file mode 100644 index 0000000000000..2e1aab60152df --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2021 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.vcn.routeselection; + +import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; + +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.calculatePriorityClass; +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; +import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnCellUnderlyingNetworkPriority; +import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnManager; +import android.net.vcn.VcnWifiUnderlyingNetworkPriority; +import android.os.ParcelUuid; +import android.os.PersistableBundle; +import android.os.test.TestLooper; +import android.telephony.TelephonyManager; +import android.util.ArraySet; + +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Set; +import java.util.UUID; + +public class NetworkPriorityClassifierTest { + private static final String SSID = "TestWifi"; + private static final String SSID_OTHER = "TestWifiOther"; + private static final String PLMN_ID = "123456"; + private static final String PLMN_ID_OTHER = "234567"; + + private static final int SUB_ID = 1; + private static final int WIFI_RSSI = -60; + private static final int WIFI_RSSI_HIGH = -50; + private static final int WIFI_RSSI_LOW = -80; + private static final int CARRIER_ID = 1; + private static final int CARRIER_ID_OTHER = 2; + + private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); + + private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSignalStrength(WIFI_RSSI) + .setSsid(SSID) + .build(); + + private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER = + new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build(); + private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setSubscriptionIds(Set.of(SUB_ID)) + .setNetworkSpecifier(TEL_NETWORK_SPECIFIER) + .build(); + + private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface"); + + @Mock private Network mNetwork; + @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; + @Mock private TelephonyManager mTelephonyManager; + + private TestLooper mTestLooper; + private VcnContext mVcnContext; + private UnderlyingNetworkRecord mWifiNetworkRecord; + private UnderlyingNetworkRecord mCellNetworkRecord; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final Context mockContext = mock(Context.class); + mTestLooper = new TestLooper(); + mVcnContext = + spy( + new VcnContext( + mockContext, + mTestLooper.getLooper(), + mock(VcnNetworkProvider.class), + false /* isInTestMode */)); + doNothing().when(mVcnContext).ensureRunningOnLooperThread(); + + mWifiNetworkRecord = + new UnderlyingNetworkRecord( + mNetwork, + WIFI_NETWORK_CAPABILITIES, + LINK_PROPERTIES, + false /* isBlocked */); + + mCellNetworkRecord = + new UnderlyingNetworkRecord( + mNetwork, + CELL_NETWORK_CAPABILITIES, + LINK_PROPERTIES, + false /* isBlocked */); + + setupSystemService( + mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); + when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager); + when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID); + when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID); + } + + @Test + public void testMatchWithoutNotMeteredBit() { + final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority = + new VcnWifiUnderlyingNetworkPriority.Builder() + .setNetworkQuality(NETWORK_QUALITY_OK) + .setAllowMetered(false /* allowMetered */) + .build(); + + assertFalse( + checkMatchesPriorityRule( + mVcnContext, + wifiNetworkPriority, + mWifiNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot, + null /* currentlySelecetd */, + null /* carrierConfig */)); + } + + private void verifyMatchWifi( + boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) { + final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority = + new VcnWifiUnderlyingNetworkPriority.Builder() + .setNetworkQuality(NETWORK_QUALITY_OK) + .setAllowMetered(true /* allowMetered */) + .build(); + final UnderlyingNetworkRecord selectedNetworkRecord = + isSelectedNetwork ? mWifiNetworkRecord : null; + assertEquals( + expectMatch, + checkMatchesWifiPriorityRule( + wifiNetworkPriority, + mWifiNetworkRecord, + selectedNetworkRecord, + carrierConfig)); + } + + @Test + public void testMatchSelectedWifi() { + verifyMatchWifi( + true /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */); + } + + @Test + public void testMatchSelectedWifiBelowRssiThreshold() { + final PersistableBundle carrierConfig = new PersistableBundle(); + carrierConfig.putInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH); + carrierConfig.putInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH); + + verifyMatchWifi(true /* isSelectedNetwork */, carrierConfig, false /* expectMatch */); + } + + @Test + public void testMatchUnselectedWifi() { + verifyMatchWifi( + false /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */); + } + + @Test + public void testMatchUnselectedWifiBelowRssiThreshold() { + final PersistableBundle carrierConfig = new PersistableBundle(); + carrierConfig.putInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH); + + verifyMatchWifi(false /* isSelectedNetwork */, carrierConfig, false /* expectMatch */); + } + + private void verifyMatchWifiWithSsid(boolean useMatchedSsid, boolean expectMatch) { + final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER; + final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority = + new VcnWifiUnderlyingNetworkPriority.Builder() + .setNetworkQuality(NETWORK_QUALITY_OK) + .setAllowMetered(true /* allowMetered */) + .setSsid(nwPrioritySsid) + .build(); + + assertEquals( + expectMatch, + checkMatchesWifiPriorityRule( + wifiNetworkPriority, + mWifiNetworkRecord, + null /* currentlySelecetd */, + null /* carrierConfig */)); + } + + @Test + public void testMatchWifiWithSsid() { + verifyMatchWifiWithSsid(true /* useMatchedSsid */, true /* expectMatch */); + } + + @Test + public void testMatchWifiFailWithWrongSsid() { + verifyMatchWifiWithSsid(false /* useMatchedSsid */, false /* expectMatch */); + } + + private static VcnCellUnderlyingNetworkPriority.Builder getCellNetworkPriorityBuilder() { + return new VcnCellUnderlyingNetworkPriority.Builder() + .setNetworkQuality(NETWORK_QUALITY_OK) + .setAllowMetered(true /* allowMetered */) + .setAllowRoaming(true /* allowRoaming */); + } + + @Test + public void testMatchMacroCell() { + assertTrue( + checkMatchesCellPriorityRule( + mVcnContext, + getCellNetworkPriorityBuilder().build(), + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchOpportunisticCell() { + final VcnCellUnderlyingNetworkPriority opportunisticCellNetworkPriority = + getCellNetworkPriorityBuilder() + .setRequireOpportunistic(true /* requireOpportunistic */) + .build(); + + when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true); + when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>()); + + assertTrue( + checkMatchesCellPriorityRule( + mVcnContext, + opportunisticCellNetworkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + private void verifyMatchMacroCellWithAllowedPlmnIds( + boolean useMatchedPlmnId, boolean expectMatch) { + final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER; + final VcnCellUnderlyingNetworkPriority networkPriority = + getCellNetworkPriorityBuilder() + .setAllowedPlmnIds(Set.of(networkPriorityPlmnId)) + .build(); + + assertEquals( + expectMatch, + checkMatchesCellPriorityRule( + mVcnContext, + networkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchMacroCellWithAllowedPlmnIds() { + verifyMatchMacroCellWithAllowedPlmnIds(true /* useMatchedPlmnId */, true /* expectMatch */); + } + + @Test + public void testMatchMacroCellFailWithDisallowedPlmnIds() { + verifyMatchMacroCellWithAllowedPlmnIds( + false /* useMatchedPlmnId */, false /* expectMatch */); + } + + private void verifyMatchMacroCellWithAllowedSpecificCarrierIds( + boolean useMatchedCarrierId, boolean expectMatch) { + final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER; + final VcnCellUnderlyingNetworkPriority networkPriority = + getCellNetworkPriorityBuilder() + .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId)) + .build(); + + assertEquals( + expectMatch, + checkMatchesCellPriorityRule( + mVcnContext, + networkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + @Test + public void testMatchMacroCellWithAllowedSpecificCarrierIds() { + verifyMatchMacroCellWithAllowedSpecificCarrierIds( + true /* useMatchedCarrierId */, true /* expectMatch */); + } + + @Test + public void testMatchMacroCellFailWithDisallowedSpecificCarrierIds() { + verifyMatchMacroCellWithAllowedSpecificCarrierIds( + false /* useMatchedCarrierId */, false /* expectMatch */); + } + + @Test + public void testMatchWifiFailWithoutNotRoamingBit() { + final VcnCellUnderlyingNetworkPriority networkPriority = + getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build(); + + assertFalse( + checkMatchesCellPriorityRule( + mVcnContext, + networkPriority, + mCellNetworkRecord, + SUB_GROUP, + mSubscriptionSnapshot)); + } + + private void verifyCalculatePriorityClass( + UnderlyingNetworkRecord networkRecord, int expectedIndex) { + final int priorityIndex = + calculatePriorityClass( + mVcnContext, + networkRecord, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES, + SUB_GROUP, + mSubscriptionSnapshot, + null /* currentlySelected */, + null /* carrierConfig */); + + assertEquals(expectedIndex, priorityIndex); + } + + @Test + public void testCalculatePriorityClass() throws Exception { + verifyCalculatePriorityClass(mCellNetworkRecord, 2); + } + + @Test + public void testCalculatePriorityClassFailToMatchAny() throws Exception { + final NetworkCapabilities nc = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSignalStrength(WIFI_RSSI_LOW) + .setSsid(SSID) + .build(); + final UnderlyingNetworkRecord wifiNetworkRecord = + new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */); + + verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY); + } +} 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 c954cb84df7fd..fad9669911bb1 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java @@ -42,6 +42,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; @@ -145,7 +146,11 @@ public class UnderlyingNetworkControllerTest { mUnderlyingNetworkController = new UnderlyingNetworkController( - mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); } private void resetVcnContext() { @@ -153,7 +158,8 @@ public class UnderlyingNetworkControllerTest { doNothing().when(mVcnContext).ensureRunningOnLooperThread(); } - private static LinkProperties getLinkPropertiesWithName(String iface) { + // Package private for use in NetworkPriorityClassifierTest + static LinkProperties getLinkPropertiesWithName(String iface) { LinkProperties linkProperties = new LinkProperties(); linkProperties.setInterfaceName(iface); return linkProperties; @@ -182,7 +188,11 @@ public class UnderlyingNetworkControllerTest { true /* isInTestMode */); new UnderlyingNetworkController( - vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); + vcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); verify(cm) .registerNetworkCallback( @@ -345,6 +355,17 @@ public class UnderlyingNetworkControllerTest { return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES); } + private static NetworkCapabilities buildResponseNwCaps( + NetworkCapabilities requestNetworkCaps, Set netCapsSubIds) { + final TelephonyNetworkSpecifier telephonyNetworkSpecifier = + new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(netCapsSubIds.iterator().next()) + .build(); + return new NetworkCapabilities.Builder(requestNetworkCaps) + .setNetworkSpecifier(telephonyNetworkSpecifier) + .build(); + } + private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback( NetworkCapabilities networkCapabilities) { verify(mConnectivityManager) @@ -355,14 +376,17 @@ public class UnderlyingNetworkControllerTest { UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.getValue(); cb.onAvailable(mNetwork); - cb.onCapabilitiesChanged(mNetwork, networkCapabilities); + + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(networkCapabilities, INITIAL_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES); cb.onBlockedStatusChanged(mNetwork, false /* isFalse */); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - networkCapabilities, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); @@ -373,12 +397,14 @@ public class UnderlyingNetworkControllerTest { public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - UPDATED_NETWORK_CAPABILITIES, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); @@ -393,7 +419,7 @@ public class UnderlyingNetworkControllerTest { UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - INITIAL_NETWORK_CAPABILITIES, + buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), UPDATED_LINK_PROPERTIES, false /* isBlocked */); verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); @@ -403,19 +429,21 @@ public class UnderlyingNetworkControllerTest { public void testRecordTrackerCallbackNotifiedForNetworkSuspended() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(SUSPENDED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - SUSPENDED_NETWORK_CAPABILITIES, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); verify(mNetworkControllerCb, times(1)) .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. - cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); verify(mNetworkControllerCb, times(1)) .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @@ -425,19 +453,21 @@ public class UnderlyingNetworkControllerTest { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES); - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - INITIAL_NETWORK_CAPABILITIES, + responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); verify(mNetworkControllerCb, times(1)) .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); verify(mNetworkControllerCb, times(1)) .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @@ -451,7 +481,7 @@ public class UnderlyingNetworkControllerTest { UnderlyingNetworkRecord expectedRecord = new UnderlyingNetworkRecord( mNetwork, - INITIAL_NETWORK_CAPABILITIES, + buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), INITIAL_LINK_PROPERTIES, true /* isBlocked */); verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); @@ -470,7 +500,8 @@ public class UnderlyingNetworkControllerTest { public void testRecordTrackerCallbackIgnoresDuplicateRecord() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + cb.onCapabilitiesChanged( + mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS)); // Verify no more calls to the UnderlyingNetworkControllerCallback when the // UnderlyingNetworkRecord does not actually change @@ -482,7 +513,8 @@ public class UnderlyingNetworkControllerTest { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); mUnderlyingNetworkController.teardown(); - cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + cb.onCapabilitiesChanged( + mNetwork, buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, INITIAL_SUB_IDS)); // Verify that the only call was during onAvailable() verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());