Calculate priority based on caller configurations

Update NetworkPriorityClassifier to calculate priority
according to the VcnUnderlyingNetworkPriority list
configured in VcnGatewayConnectionConfig

Bug: 206044122
Test: atest FrameworksVcnTests(new)
Test: atest CtsVcnTestCses
Change-Id: I55afa916af44db655a568aae2488877f2c177d59
This commit is contained in:
Yan Yan
2021-11-29 16:15:06 -08:00
parent fee736f47c
commit 738ed7b7b2
11 changed files with 759 additions and 110 deletions

View File

@@ -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<Builder> {
@NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();

View File

@@ -160,7 +160,9 @@ public final class VcnGatewayConnectionConfig {
TimeUnit.MINUTES.toMillis(15)
};
private static final LinkedHashSet<VcnUnderlyingNetworkPriority>
/** @hide */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static final LinkedHashSet<VcnUnderlyingNetworkPriority>
DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
static {

View File

@@ -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<String> 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() {

View File

@@ -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() {

View File

@@ -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. */

View File

@@ -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<String> 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:
*
* <ol>
* <li>Opportunistic cellular
* <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
* <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
* <li>Macro cellular
* <li>Any others
* </ol>
*/
static int calculatePriorityClass(
/** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
public static int calculatePriorityClass(
VcnContext vcnContext,
UnderlyingNetworkRecord networkRecord,
LinkedHashSet<VcnUnderlyingNetworkPriority> 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);

View File

@@ -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<UnderlyingNetworkRecord> 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();

View File

@@ -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<UnderlyingNetworkRecord> getComparator(
VcnContext vcnContext,
LinkedHashSet<VcnUnderlyingNetworkPriority> 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<VcnUnderlyingNetworkPriority> 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);

View File

@@ -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());

View File

@@ -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);
}
}

View File

@@ -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<Integer> 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());