Merge "Prefer default Internet network for upstream tethering."

This commit is contained in:
Treehugger Robot
2018-06-25 12:08:46 +00:00
committed by Gerrit Code Review
9 changed files with 273 additions and 128 deletions

View File

@@ -488,13 +488,21 @@
Note also: the order of this is important. The first upstream type Note also: the order of this is important. The first upstream type
for which a satisfying network exists is used. for which a satisfying network exists is used.
--> -->
<integer-array translatable="false" name="config_tether_upstream_types"> <integer-array translatable="false" name="config_tether_upstream_types">
<item>1</item> <item>1</item>
<item>7</item> <item>7</item>
<item>0</item> <item>0</item>
</integer-array> </integer-array>
<!-- When true, the tethering upstream network follows the current default
Internet network (except when the current default network is mobile,
in which case a DUN network will be used if required).
When true, overrides the config_tether_upstream_types setting above.
-->
<bool translatable="false" name="config_tether_upstream_automatic">false</bool>
<!-- If the DUN connection for this CDMA device supports more than just DUN --> <!-- If the DUN connection for this CDMA device supports more than just DUN -->
<!-- traffic you should list them here. --> <!-- traffic you should list them here. -->
<!-- If this device is not CDMA this is ignored. If this list is empty on --> <!-- If this device is not CDMA this is ignored. If this list is empty on -->

View File

@@ -1732,6 +1732,7 @@
<java-symbol type="array" name="config_tether_bluetooth_regexs" /> <java-symbol type="array" name="config_tether_bluetooth_regexs" />
<java-symbol type="array" name="config_tether_dhcp_range" /> <java-symbol type="array" name="config_tether_dhcp_range" />
<java-symbol type="array" name="config_tether_upstream_types" /> <java-symbol type="array" name="config_tether_upstream_types" />
<java-symbol type="bool" name="config_tether_upstream_automatic" />
<java-symbol type="array" name="config_tether_apndata" /> <java-symbol type="array" name="config_tether_apndata" />
<java-symbol type="array" name="config_tether_usb_regexs" /> <java-symbol type="array" name="config_tether_usb_regexs" />
<java-symbol type="array" name="config_tether_wifi_regexs" /> <java-symbol type="array" name="config_tether_wifi_regexs" />

View File

@@ -868,6 +868,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
public boolean isTetheringSupported() { public boolean isTetheringSupported() {
return ConnectivityService.this.isTetheringSupported(); return ConnectivityService.this.isTetheringSupported();
} }
@Override
public NetworkRequest getDefaultNetworkRequest() {
return mDefaultRequest;
}
}; };
return new Tethering(mContext, mNetd, mStatsService, mPolicyManager, return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
IoThread.get().getLooper(), new MockableSystemProperties(), IoThread.get().getLooper(), new MockableSystemProperties(),
@@ -885,7 +889,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
private NetworkRequest createDefaultInternetRequestForTransport( private NetworkRequest createDefaultInternetRequestForTransport(
int transportType, NetworkRequest.Type type) { int transportType, NetworkRequest.Type type) {
NetworkCapabilities netCap = new NetworkCapabilities(); final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET); netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED); netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (transportType > -1) { if (transportType > -1) {

View File

@@ -1349,8 +1349,11 @@ public class Tethering extends BaseNetworkObserver {
// do not currently know how to watch for changes in DUN settings. // do not currently know how to watch for changes in DUN settings.
maybeUpdateConfiguration(); maybeUpdateConfiguration();
final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType( final TetheringConfiguration config = mConfig;
mConfig.preferredUpstreamIfaceTypes); final NetworkState ns = (config.chooseUpstreamAutomatically)
? mUpstreamNetworkMonitor.getCurrentPreferredUpstream()
: mUpstreamNetworkMonitor.selectPreferredUpstreamType(
config.preferredUpstreamIfaceTypes);
if (ns == null) { if (ns == null) {
if (tryCell) { if (tryCell) {
mUpstreamNetworkMonitor.registerMobileNetworkRequest(); mUpstreamNetworkMonitor.registerMobileNetworkRequest();
@@ -1379,9 +1382,7 @@ public class Tethering extends BaseNetworkObserver {
} }
notifyDownstreamsOfNewUpstreamIface(ifaces); notifyDownstreamsOfNewUpstreamIface(ifaces);
if (ns != null && pertainsToCurrentUpstream(ns)) { if (ns != null && pertainsToCurrentUpstream(ns)) {
// If we already have NetworkState for this network examine // If we already have NetworkState for this network update it immediately.
// it immediately, because there likely will be no second
// EVENT_ON_AVAILABLE (it was already received).
handleNewUpstreamNetworkState(ns); handleNewUpstreamNetworkState(ns);
} else if (mCurrentUpstreamIfaceSet == null) { } else if (mCurrentUpstreamIfaceSet == null) {
// There are no available upstream networks. // There are no available upstream networks.
@@ -1497,15 +1498,6 @@ public class Tethering extends BaseNetworkObserver {
} }
switch (arg1) { switch (arg1) {
case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE:
// The default network changed, or DUN connected
// before this callback was processed. Updates
// for the current NetworkCapabilities and
// LinkProperties have been requested (default
// request) or are being sent shortly (DUN). Do
// nothing until they arrive; if no updates
// arrive there's nothing to do.
break;
case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES: case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
handleNewUpstreamNetworkState(ns); handleNewUpstreamNetworkState(ns);
break; break;
@@ -1538,7 +1530,7 @@ public class Tethering extends BaseNetworkObserver {
} }
mSimChange.startListening(); mSimChange.startListening();
mUpstreamNetworkMonitor.start(); mUpstreamNetworkMonitor.start(mDeps.getDefaultNetworkRequest());
// TODO: De-duplicate with updateUpstreamWanted() below. // TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) { if (upstreamWanted()) {

View File

@@ -27,6 +27,7 @@ import static com.android.internal.R.array.config_tether_dhcp_range;
import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_usb_regexs;
import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_upstream_types;
import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs;
import static com.android.internal.R.bool.config_tether_upstream_automatic;
import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
import android.content.Context; import android.content.Context;
@@ -86,6 +87,7 @@ public class TetheringConfiguration {
public final String[] tetherableBluetoothRegexs; public final String[] tetherableBluetoothRegexs;
public final int dunCheck; public final int dunCheck;
public final boolean isDunRequired; public final boolean isDunRequired;
public final boolean chooseUpstreamAutomatically;
public final Collection<Integer> preferredUpstreamIfaceTypes; public final Collection<Integer> preferredUpstreamIfaceTypes;
public final String[] dhcpRanges; public final String[] dhcpRanges;
public final String[] defaultIPv4DNS; public final String[] defaultIPv4DNS;
@@ -106,6 +108,7 @@ public class TetheringConfiguration {
dunCheck = checkDunRequired(ctx); dunCheck = checkDunRequired(ctx);
configLog.log("DUN check returned: " + dunCheckString(dunCheck)); configLog.log("DUN check returned: " + dunCheckString(dunCheck));
chooseUpstreamAutomatically = getResourceBoolean(ctx, config_tether_upstream_automatic);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck); preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN); isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
@@ -142,6 +145,8 @@ public class TetheringConfiguration {
pw.print("isDunRequired: "); pw.print("isDunRequired: ");
pw.println(isDunRequired); pw.println(isDunRequired);
pw.print("chooseUpstreamAutomatically: ");
pw.println(chooseUpstreamAutomatically);
dumpStringArray(pw, "preferredUpstreamIfaceTypes", dumpStringArray(pw, "preferredUpstreamIfaceTypes",
preferredUpstreamNames(preferredUpstreamIfaceTypes)); preferredUpstreamNames(preferredUpstreamIfaceTypes));
@@ -160,6 +165,7 @@ public class TetheringConfiguration {
sj.add(String.format("tetherableBluetoothRegexs:%s", sj.add(String.format("tetherableBluetoothRegexs:%s",
makeString(tetherableBluetoothRegexs))); makeString(tetherableBluetoothRegexs)));
sj.add(String.format("isDunRequired:%s", isDunRequired)); sj.add(String.format("isDunRequired:%s", isDunRequired));
sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
sj.add(String.format("preferredUpstreamIfaceTypes:%s", sj.add(String.format("preferredUpstreamIfaceTypes:%s",
makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes)))); makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
sj.add(String.format("provisioningApp:%s", makeString(provisioningApp))); sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
@@ -286,6 +292,14 @@ public class TetheringConfiguration {
} }
} }
private static boolean getResourceBoolean(Context ctx, int resId) {
try {
return ctx.getResources().getBoolean(resId);
} catch (Resources.NotFoundException e404) {
return false;
}
}
private static String[] getResourceStringArray(Context ctx, int resId) { private static String[] getResourceStringArray(Context ctx, int resId) {
try { try {
final String[] strArray = ctx.getResources().getStringArray(resId); final String[] strArray = ctx.getResources().getStringArray(resId);

View File

@@ -18,6 +18,7 @@ package com.android.server.connectivity.tethering;
import android.content.Context; import android.content.Context;
import android.net.INetd; import android.net.INetd;
import android.net.NetworkRequest;
import android.net.ip.RouterAdvertisementDaemon; import android.net.ip.RouterAdvertisementDaemon;
import android.net.util.InterfaceParams; import android.net.util.InterfaceParams;
import android.net.util.NetdService; import android.net.util.NetdService;
@@ -64,4 +65,8 @@ public class TetheringDependencies {
public boolean isTetheringSupported() { public boolean isTetheringSupported() {
return true; return true;
} }
public NetworkRequest getDefaultNetworkRequest() {
return null;
}
} }

View File

@@ -20,6 +20,9 @@ import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
@@ -74,14 +77,13 @@ public class UpstreamNetworkMonitor {
private static final boolean DBG = false; private static final boolean DBG = false;
private static final boolean VDBG = false; private static final boolean VDBG = false;
public static final int EVENT_ON_AVAILABLE = 1; public static final int EVENT_ON_CAPABILITIES = 1;
public static final int EVENT_ON_CAPABILITIES = 2; public static final int EVENT_ON_LINKPROPERTIES = 2;
public static final int EVENT_ON_LINKPROPERTIES = 3; public static final int EVENT_ON_LOST = 3;
public static final int EVENT_ON_LOST = 4;
public static final int NOTIFY_LOCAL_PREFIXES = 10; public static final int NOTIFY_LOCAL_PREFIXES = 10;
private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_LISTEN_ALL = 1;
private static final int CALLBACK_TRACK_DEFAULT = 2; private static final int CALLBACK_DEFAULT_INTERNET = 2;
private static final int CALLBACK_MOBILE_REQUEST = 3; private static final int CALLBACK_MOBILE_REQUEST = 3;
private final Context mContext; private final Context mContext;
@@ -117,7 +119,7 @@ public class UpstreamNetworkMonitor {
mCM = cm; mCM = cm;
} }
public void start() { public void start(NetworkRequest defaultNetworkRequest) {
stop(); stop();
final NetworkRequest listenAllRequest = new NetworkRequest.Builder() final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
@@ -125,8 +127,16 @@ public class UpstreamNetworkMonitor {
mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL); mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler); cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_TRACK_DEFAULT); if (defaultNetworkRequest != null) {
cm().registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); // This is not really a "request", just a way of tracking the system default network.
// It's guaranteed not to actually bring up any networks because it's the same request
// as the ConnectivityService default request, and thus shares fate with it. We can't
// use registerDefaultNetworkCallback because it will not track the system default
// network if there is a VPN that applies to our UID.
final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest);
mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
}
} }
public void stop() { public void stop() {
@@ -225,6 +235,20 @@ public class UpstreamNetworkMonitor {
return typeStatePair.ns; return typeStatePair.ns;
} }
// Returns null if no current upstream available.
public NetworkState getCurrentPreferredUpstream() {
final NetworkState dfltState = (mDefaultInternetNetwork != null)
? mNetworkMap.get(mDefaultInternetNetwork)
: null;
if (!mDunRequired) return dfltState;
if (isNetworkUsableAndNotCellular(dfltState)) return dfltState;
// Find a DUN network. Note that code in Tethering causes a DUN request
// to be filed, but this might be moved into this class in future.
return findFirstDunNetwork(mNetworkMap.values());
}
public void setCurrentUpstream(Network upstream) { public void setCurrentUpstream(Network upstream) {
mTetheringUpstreamNetwork = upstream; mTetheringUpstreamNetwork = upstream;
} }
@@ -233,72 +257,16 @@ public class UpstreamNetworkMonitor {
return (Set<IpPrefix>) mLocalPrefixes.clone(); return (Set<IpPrefix>) mLocalPrefixes.clone();
} }
private void handleAvailable(int callbackType, Network network) { private void handleAvailable(Network network) {
if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network); if (mNetworkMap.containsKey(network)) return;
if (!mNetworkMap.containsKey(network)) { if (VDBG) Log.d(TAG, "onAvailable for " + network);
mNetworkMap.put(network, mNetworkMap.put(network, new NetworkState(null, null, null, network, null, null));
new NetworkState(null, null, null, network, null, null));
}
// Always request whatever extra information we can, in case this
// was already up when start() was called, in which case we would
// not have been notified of any information that had not changed.
switch (callbackType) {
case CALLBACK_LISTEN_ALL:
break;
case CALLBACK_TRACK_DEFAULT:
if (mDefaultNetworkCallback == null) {
// The callback was unregistered in the interval between
// ConnectivityService enqueueing onAvailable() and our
// handling of it here on the mHandler thread.
//
// Clean-up of this network entry is deferred to the
// handling of onLost() by other callbacks.
//
// These request*() calls can be deleted post oag/339444.
return;
}
mDefaultInternetNetwork = network;
break;
case CALLBACK_MOBILE_REQUEST:
if (mMobileNetworkCallback == null) {
// The callback was unregistered in the interval between
// ConnectivityService enqueueing onAvailable() and our
// handling of it here on the mHandler thread.
//
// Clean-up of this network entry is deferred to the
// handling of onLost() by other callbacks.
return;
}
break;
}
// Requesting updates for mListenAllCallback is not currently possible
// because it's a "listen". Two possible solutions to getting updates
// about networks without waiting for a change (which might never come)
// are:
//
// [1] extend request{NetworkCapabilities,LinkProperties}() to
// take a Network argument and have ConnectivityService do
// what's required (if the network satisfies the request)
//
// [2] explicitly file a NetworkRequest for each connectivity type
// listed as a preferred upstream and wait for these callbacks
// to be notified (requires tracking many more callbacks).
//
// Until this is addressed, networks that exist prior to the "listen"
// registration and which do not subsequently change will not cause
// us to learn their NetworkCapabilities nor their LinkProperties.
// TODO: If sufficient information is available to select a more
// preferable upstream, do so now and notify the target.
notifyTarget(EVENT_ON_AVAILABLE, network);
} }
private void handleNetCap(Network network, NetworkCapabilities newNc) { private void handleNetCap(int callbackType, Network network, NetworkCapabilities newNc) {
if (callbackType == CALLBACK_DEFAULT_INTERNET) mDefaultInternetNetwork = network;
final NetworkState prev = mNetworkMap.get(network); final NetworkState prev = mNetworkMap.get(network);
if (prev == null || newNc.equals(prev.networkCapabilities)) { if (prev == null || newNc.equals(prev.networkCapabilities)) {
// Ignore notifications about networks for which we have not yet // Ignore notifications about networks for which we have not yet
@@ -360,13 +328,17 @@ public class UpstreamNetworkMonitor {
} }
private void handleLost(int callbackType, Network network) { private void handleLost(int callbackType, Network network) {
if (callbackType == CALLBACK_TRACK_DEFAULT) { if (network.equals(mDefaultInternetNetwork)) {
mDefaultInternetNetwork = null; mDefaultInternetNetwork = null;
// Receiving onLost() for a default network does not necessarily // There are few TODOs within ConnectivityService's rematching code
// mean the network is gone. We wait for a separate notification // pertaining to spurious onLost() notifications.
// on either the LISTEN_ALL or MOBILE_REQUEST callbacks before //
// clearing all state. // TODO: simplify this, probably if favor of code that:
return; // - selects a new upstream if mTetheringUpstreamNetwork has
// been lost (by any callback)
// - deletes the entry from the map only when the LISTEN_ALL
// callback gets notified.
if (callbackType == CALLBACK_DEFAULT_INTERNET) return;
} }
if (!mNetworkMap.containsKey(network)) { if (!mNetworkMap.containsKey(network)) {
@@ -416,17 +388,19 @@ public class UpstreamNetworkMonitor {
@Override @Override
public void onAvailable(Network network) { public void onAvailable(Network network) {
handleAvailable(mCallbackType, network); handleAvailable(network);
} }
@Override @Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
handleNetCap(network, newNc); handleNetCap(mCallbackType, network, newNc);
} }
@Override @Override
public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
handleLinkProp(network, newLp); handleLinkProp(network, newLp);
// TODO(b/110335330): reduce the number of times this is called by
// only recomputing on the LISTEN_ALL callback.
recomputeLocalPrefixes(); recomputeLocalPrefixes();
} }
@@ -443,6 +417,8 @@ public class UpstreamNetworkMonitor {
@Override @Override
public void onLost(Network network) { public void onLost(Network network) {
handleLost(mCallbackType, network); handleLost(mCallbackType, network);
// TODO(b/110335330): reduce the number of times this is called by
// only recomputing on the LISTEN_ALL callback.
recomputeLocalPrefixes(); recomputeLocalPrefixes();
} }
} }
@@ -509,4 +485,31 @@ public class UpstreamNetworkMonitor {
if (nc == null || !nc.hasSignalStrength()) return "unknown"; if (nc == null || !nc.hasSignalStrength()) return "unknown";
return Integer.toString(nc.getSignalStrength()); return Integer.toString(nc.getSignalStrength());
} }
private static boolean isCellular(NetworkState ns) {
return (ns != null) && isCellular(ns.networkCapabilities);
}
private static boolean isCellular(NetworkCapabilities nc) {
return (nc != null) && nc.hasTransport(TRANSPORT_CELLULAR) &&
nc.hasCapability(NET_CAPABILITY_NOT_VPN);
}
private static boolean hasCapability(NetworkState ns, int netCap) {
return (ns != null) && (ns.networkCapabilities != null) &&
ns.networkCapabilities.hasCapability(netCap);
}
private static boolean isNetworkUsableAndNotCellular(NetworkState ns) {
return (ns != null) && (ns.networkCapabilities != null) && (ns.linkProperties != null) &&
!isCellular(ns.networkCapabilities);
}
private static NetworkState findFirstDunNetwork(Iterable<NetworkState> netStates) {
for (NetworkState ns : netStates) {
if (isCellular(ns) && hasCapability(ns, NET_CAPABILITY_DUN)) return ns;
}
return null;
}
} }

View File

@@ -71,6 +71,7 @@ import android.net.MacAddress;
import android.net.Network; import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkState; import android.net.NetworkState;
import android.net.NetworkUtils; import android.net.NetworkUtils;
import android.net.RouteInfo; import android.net.RouteInfo;
@@ -128,6 +129,10 @@ public class TetheringTest {
private static final String TEST_USB_IFNAME = "test_rndis0"; private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WLAN_IFNAME = "test_wlan0"; private static final String TEST_WLAN_IFNAME = "test_wlan0";
// Actual contents of the request don't matter for this test. The lack of
// any specific TRANSPORT_* is sufficient to identify this request.
private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();
@Mock private ApplicationInfo mApplicationInfo; @Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext; @Mock private Context mContext;
@Mock private INetworkManagementService mNMService; @Mock private INetworkManagementService mNMService;
@@ -238,6 +243,11 @@ public class TetheringTest {
isTetheringSupportedCalls++; isTetheringSupportedCalls++;
return true; return true;
} }
@Override
public NetworkRequest getDefaultNetworkRequest() {
return mDefaultRequest;
}
} }
private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6, private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6,
@@ -305,6 +315,8 @@ public class TetheringTest {
.thenReturn(new String[0]); .thenReturn(new String[0]);
when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
.thenReturn(new int[0]); .thenReturn(new int[0]);
when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
.thenReturn(false);
when(mNMService.listInterfaces()) when(mNMService.listInterfaces())
.thenReturn(new String[] { .thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME}); TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME});
@@ -458,6 +470,7 @@ public class TetheringTest {
} }
private void prepareUsbTethering(NetworkState upstreamState) { private void prepareUsbTethering(NetworkState upstreamState) {
when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
.thenReturn(upstreamState); .thenReturn(upstreamState);
@@ -519,7 +532,7 @@ public class TetheringTest {
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY); TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
verifyNoMoreInteractions(mWifiManager); verifyNoMoreInteractions(mWifiManager);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
verify(mUpstreamNetworkMonitor, times(1)).start(); verify(mUpstreamNetworkMonitor, times(1)).start(any(NetworkRequest.class));
// TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
@@ -655,6 +668,24 @@ public class TetheringTest {
verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
} }
@Test
public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
.thenReturn(true);
sendConfigurationChanged();
// Setup IPv6
final NetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
// UpstreamNetworkMonitor should choose upstream automatically
// (in this specific case: choose the default network).
verify(mUpstreamNetworkMonitor, times(1)).getCurrentPreferredUpstream();
verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
}
@Test @Test
public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception { public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception {
workingLocalOnlyHotspotEnrichedApBroadcast(true); workingLocalOnlyHotspotEnrichedApBroadcast(true);
@@ -718,7 +749,7 @@ public class TetheringTest {
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
verifyNoMoreInteractions(mWifiManager); verifyNoMoreInteractions(mWifiManager);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
verify(mUpstreamNetworkMonitor, times(1)).start(); verify(mUpstreamNetworkMonitor, times(1)).start(any(NetworkRequest.class));
// In tethering mode, in the default configuration, an explicit request // In tethering mode, in the default configuration, an explicit request
// for a mobile network is also made. // for a mobile network is also made.
verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest(); verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();

View File

@@ -31,6 +31,7 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -73,6 +74,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -84,6 +86,10 @@ public class UpstreamNetworkMonitorTest {
private static final boolean INCLUDES = true; private static final boolean INCLUDES = true;
private static final boolean EXCLUDES = false; private static final boolean EXCLUDES = false;
// Actual contents of the request don't matter for this test. The lack of
// any specific TRANSPORT_* is sufficient to identify this request.
private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();
@Mock private Context mContext; @Mock private Context mContext;
@Mock private IConnectivityManager mCS; @Mock private IConnectivityManager mCS;
@Mock private SharedLog mLog; @Mock private SharedLog mLog;
@@ -112,6 +118,13 @@ public class UpstreamNetworkMonitorTest {
} }
} }
@Test
public void testStopWithoutStartIsNonFatal() {
mUNM.stop();
mUNM.stop();
mUNM.stop();
}
@Test @Test
public void testDoesNothingBeforeStarted() { public void testDoesNothingBeforeStarted() {
assertTrue(mCM.hasNoCallbacks()); assertTrue(mCM.hasNoCallbacks());
@@ -127,7 +140,7 @@ public class UpstreamNetworkMonitorTest {
public void testDefaultNetworkIsTracked() throws Exception { public void testDefaultNetworkIsTracked() throws Exception {
assertEquals(0, mCM.trackingDefault.size()); assertEquals(0, mCM.trackingDefault.size());
mUNM.start(); mUNM.start(mDefaultRequest);
assertEquals(1, mCM.trackingDefault.size()); assertEquals(1, mCM.trackingDefault.size());
mUNM.stop(); mUNM.stop();
@@ -138,7 +151,7 @@ public class UpstreamNetworkMonitorTest {
public void testListensForAllNetworks() throws Exception { public void testListensForAllNetworks() throws Exception {
assertTrue(mCM.listening.isEmpty()); assertTrue(mCM.listening.isEmpty());
mUNM.start(); mUNM.start(mDefaultRequest);
assertFalse(mCM.listening.isEmpty()); assertFalse(mCM.listening.isEmpty());
assertTrue(mCM.isListeningForAll()); assertTrue(mCM.isListeningForAll());
@@ -148,9 +161,11 @@ public class UpstreamNetworkMonitorTest {
@Test @Test
public void testCallbacksRegistered() { public void testCallbacksRegistered() {
mUNM.start(); mUNM.start(mDefaultRequest);
verify(mCM, times(1)).registerNetworkCallback(any(), any(), any()); verify(mCM, times(1)).registerNetworkCallback(
verify(mCM, times(1)).registerDefaultNetworkCallback(any(), any()); any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
verify(mCM, times(1)).requestNetwork(
eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
mUNM.stop(); mUNM.stop();
verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class)); verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
@@ -161,7 +176,7 @@ public class UpstreamNetworkMonitorTest {
assertFalse(mUNM.mobileNetworkRequested()); assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size()); assertEquals(0, mCM.requested.size());
mUNM.start(); mUNM.start(mDefaultRequest);
assertFalse(mUNM.mobileNetworkRequested()); assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size()); assertEquals(0, mCM.requested.size());
@@ -184,17 +199,17 @@ public class UpstreamNetworkMonitorTest {
assertFalse(mUNM.mobileNetworkRequested()); assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size()); assertEquals(0, mCM.requested.size());
mUNM.start(); mUNM.start(mDefaultRequest);
verify(mCM, Mockito.times(1)).registerNetworkCallback( verify(mCM, times(1)).registerNetworkCallback(
any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
verify(mCM, Mockito.times(1)).registerDefaultNetworkCallback( verify(mCM, times(1)).requestNetwork(
any(NetworkCallback.class), any(Handler.class)); eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
assertFalse(mUNM.mobileNetworkRequested()); assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size()); assertEquals(0, mCM.requested.size());
mUNM.updateMobileRequiresDun(true); mUNM.updateMobileRequiresDun(true);
mUNM.registerMobileNetworkRequest(); mUNM.registerMobileNetworkRequest();
verify(mCM, Mockito.times(1)).requestNetwork( verify(mCM, times(1)).requestNetwork(
any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(), any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(),
any(Handler.class)); any(Handler.class));
@@ -222,7 +237,7 @@ public class UpstreamNetworkMonitorTest {
assertFalse(mUNM.mobileNetworkRequested()); assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size()); assertEquals(0, mCM.requested.size());
mUNM.start(); mUNM.start(mDefaultRequest);
assertFalse(mUNM.mobileNetworkRequested()); assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size()); assertEquals(0, mCM.requested.size());
@@ -242,7 +257,7 @@ public class UpstreamNetworkMonitorTest {
@Test @Test
public void testUpdateMobileRequiresDun() throws Exception { public void testUpdateMobileRequiresDun() throws Exception {
mUNM.start(); mUNM.start(mDefaultRequest);
// Test going from no-DUN to DUN correctly re-registers callbacks. // Test going from no-DUN to DUN correctly re-registers callbacks.
mUNM.updateMobileRequiresDun(false); mUNM.updateMobileRequiresDun(false);
@@ -270,7 +285,7 @@ public class UpstreamNetworkMonitorTest {
final Collection<Integer> preferredTypes = new ArrayList<>(); final Collection<Integer> preferredTypes = new ArrayList<>();
preferredTypes.add(TYPE_WIFI); preferredTypes.add(TYPE_WIFI);
mUNM.start(); mUNM.start(mDefaultRequest);
// There are no networks, so there is nothing to select. // There are no networks, so there is nothing to select.
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -333,9 +348,48 @@ public class UpstreamNetworkMonitorTest {
assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
} }
@Test
public void testGetCurrentPreferredUpstream() throws Exception {
mUNM.start(mDefaultRequest);
mUNM.updateMobileRequiresDun(false);
// [0] Mobile connects, DUN not required -> mobile selected.
final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
cellAgent.fakeConnect();
mCM.makeDefaultNetwork(cellAgent);
assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
// [1] WiFi connects but not validated/promoted to default -> mobile selected.
final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
wifiAgent.fakeConnect();
assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
// [2] WiFi validates and is promoted to the default network -> WiFi selected.
mCM.makeDefaultNetwork(wifiAgent);
assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
// [3] DUN required, no other changes -> WiFi still selected
mUNM.updateMobileRequiresDun(true);
assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
// [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected.
mCM.makeDefaultNetwork(cellAgent);
assertEquals(null, mUNM.getCurrentPreferredUpstream());
// TODO: make sure that a DUN request has been filed. This is currently
// triggered by code over in Tethering, but once that has been moved
// into UNM we should test for this here.
// [5] DUN network arrives -> DUN selected
final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
dunAgent.fakeConnect();
assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
}
@Test @Test
public void testLocalPrefixes() throws Exception { public void testLocalPrefixes() throws Exception {
mUNM.start(); mUNM.start(mDefaultRequest);
// [0] Test minimum set of local prefixes. // [0] Test minimum set of local prefixes.
Set<IpPrefix> local = mUNM.getLocalPrefixes(); Set<IpPrefix> local = mUNM.getLocalPrefixes();
@@ -345,7 +399,7 @@ public class UpstreamNetworkMonitorTest {
// [1] Pretend Wi-Fi connects. // [1] Pretend Wi-Fi connects.
final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
final LinkProperties wifiLp = new LinkProperties(); final LinkProperties wifiLp = wifiAgent.linkProperties;
wifiLp.setInterfaceName("wlan0"); wifiLp.setInterfaceName("wlan0");
final String[] WIFI_ADDRS = { final String[] WIFI_ADDRS = {
"fe80::827a:bfff:fe6f:374d", "100.112.103.18", "fe80::827a:bfff:fe6f:374d", "100.112.103.18",
@@ -358,7 +412,7 @@ public class UpstreamNetworkMonitorTest {
wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr)); wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
} }
wifiAgent.fakeConnect(); wifiAgent.fakeConnect();
wifiAgent.sendLinkProperties(wifiLp); wifiAgent.sendLinkProperties();
local = mUNM.getLocalPrefixes(); local = mUNM.getLocalPrefixes();
assertPrefixSet(local, INCLUDES, alreadySeen); assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -372,7 +426,7 @@ public class UpstreamNetworkMonitorTest {
// [2] Pretend mobile connects. // [2] Pretend mobile connects.
final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
final LinkProperties cellLp = new LinkProperties(); final LinkProperties cellLp = cellAgent.linkProperties;
cellLp.setInterfaceName("rmnet_data0"); cellLp.setInterfaceName("rmnet_data0");
final String[] CELL_ADDRS = { final String[] CELL_ADDRS = {
"10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d", "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
@@ -382,7 +436,7 @@ public class UpstreamNetworkMonitorTest {
cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
} }
cellAgent.fakeConnect(); cellAgent.fakeConnect();
cellAgent.sendLinkProperties(cellLp); cellAgent.sendLinkProperties();
local = mUNM.getLocalPrefixes(); local = mUNM.getLocalPrefixes();
assertPrefixSet(local, INCLUDES, alreadySeen); assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -394,17 +448,18 @@ public class UpstreamNetworkMonitorTest {
// [3] Pretend DUN connects. // [3] Pretend DUN connects.
final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
final LinkProperties dunLp = new LinkProperties(); dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
final LinkProperties dunLp = dunAgent.linkProperties;
dunLp.setInterfaceName("rmnet_data1"); dunLp.setInterfaceName("rmnet_data1");
final String[] DUN_ADDRS = { final String[] DUN_ADDRS = {
"192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d", "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
}; };
for (String addrStr : DUN_ADDRS) { for (String addrStr : DUN_ADDRS) {
final String cidr = addrStr.contains(":") ? "/64" : "/27"; final String cidr = addrStr.contains(":") ? "/64" : "/27";
cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); dunLp.addLinkAddress(new LinkAddress(addrStr + cidr));
} }
dunAgent.fakeConnect(); dunAgent.fakeConnect();
dunAgent.sendLinkProperties(dunLp); dunAgent.sendLinkProperties();
local = mUNM.getLocalPrefixes(); local = mUNM.getLocalPrefixes();
assertPrefixSet(local, INCLUDES, alreadySeen); assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -442,6 +497,7 @@ public class UpstreamNetworkMonitorTest {
public static class TestConnectivityManager extends ConnectivityManager { public static class TestConnectivityManager extends ConnectivityManager {
public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>(); public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
public Set<NetworkCallback> trackingDefault = new HashSet<>(); public Set<NetworkCallback> trackingDefault = new HashSet<>();
public TestNetworkAgent defaultNetwork = null;
public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>(); public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>(); public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>(); public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
@@ -483,12 +539,34 @@ public class UpstreamNetworkMonitorTest {
int getNetworkId() { return ++mNetworkId; } int getNetworkId() { return ++mNetworkId; }
void makeDefaultNetwork(TestNetworkAgent agent) {
if (Objects.equals(defaultNetwork, agent)) return;
final TestNetworkAgent formerDefault = defaultNetwork;
defaultNetwork = agent;
for (NetworkCallback cb : trackingDefault) {
if (defaultNetwork != null) {
cb.onAvailable(defaultNetwork.networkId);
cb.onCapabilitiesChanged(
defaultNetwork.networkId, defaultNetwork.networkCapabilities);
cb.onLinkPropertiesChanged(
defaultNetwork.networkId, defaultNetwork.linkProperties);
}
}
}
@Override @Override
public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
assertFalse(allCallbacks.containsKey(cb)); assertFalse(allCallbacks.containsKey(cb));
allCallbacks.put(cb, h); allCallbacks.put(cb, h);
assertFalse(requested.containsKey(cb)); if (mDefaultRequest.equals(req)) {
requested.put(cb, req); assertFalse(trackingDefault.contains(cb));
trackingDefault.add(cb);
} else {
assertFalse(requested.containsKey(cb));
requested.put(cb, req);
}
} }
@Override @Override
@@ -524,10 +602,7 @@ public class UpstreamNetworkMonitorTest {
@Override @Override
public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) { public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
assertFalse(allCallbacks.containsKey(cb)); fail("Should never be called.");
allCallbacks.put(cb, h);
assertFalse(trackingDefault.contains(cb));
trackingDefault.add(cb);
} }
@Override @Override
@@ -561,6 +636,7 @@ public class UpstreamNetworkMonitorTest {
public final Network networkId; public final Network networkId;
public final int transportType; public final int transportType;
public final NetworkCapabilities networkCapabilities; public final NetworkCapabilities networkCapabilities;
public final LinkProperties linkProperties;
public TestNetworkAgent(TestConnectivityManager cm, int transportType) { public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
this.cm = cm; this.cm = cm;
@@ -569,12 +645,14 @@ public class UpstreamNetworkMonitorTest {
networkCapabilities = new NetworkCapabilities(); networkCapabilities = new NetworkCapabilities();
networkCapabilities.addTransportType(transportType); networkCapabilities.addTransportType(transportType);
networkCapabilities.addCapability(NET_CAPABILITY_INTERNET); networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
linkProperties = new LinkProperties();
} }
public void fakeConnect() { public void fakeConnect() {
for (NetworkCallback cb : cm.listening.keySet()) { for (NetworkCallback cb : cm.listening.keySet()) {
cb.onAvailable(networkId); cb.onAvailable(networkId);
cb.onCapabilitiesChanged(networkId, copy(networkCapabilities)); cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
} }
} }
@@ -584,11 +662,16 @@ public class UpstreamNetworkMonitorTest {
} }
} }
public void sendLinkProperties(LinkProperties lp) { public void sendLinkProperties() {
for (NetworkCallback cb : cm.listening.keySet()) { for (NetworkCallback cb : cm.listening.keySet()) {
cb.onLinkPropertiesChanged(networkId, lp); cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
} }
} }
@Override
public String toString() {
return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities);
}
} }
public static class TestStateMachine extends StateMachine { public static class TestStateMachine extends StateMachine {
@@ -618,6 +701,10 @@ public class UpstreamNetworkMonitorTest {
return new NetworkCapabilities(nc); return new NetworkCapabilities(nc);
} }
static LinkProperties copy(LinkProperties lp) {
return new LinkProperties(lp);
}
static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) { static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
final Set<String> expectedSet = new HashSet<>(); final Set<String> expectedSet = new HashSet<>();
Collections.addAll(expectedSet, expected); Collections.addAll(expectedSet, expected);