diff options
author | Sal Savage <salsavage@google.com> | 2019-01-09 15:46:08 -0800 |
---|---|---|
committer | Sal Savage <salsavage@google.com> | 2019-01-15 11:56:50 -0800 |
commit | 79929d71005b8af04e7489b32728ad4a5c231eb7 (patch) | |
tree | d456c93de4bc0f84b52cd7b318a75848a29723f2 | |
parent | 8af1c3493fa8a855813a538135e274c97aa0a99b (diff) | |
download | ethernet-79929d71005b8af04e7489b32728ad4a5c231eb7.tar.gz |
Allow network transport type override
Bug: b/112588045
Test: atest EthernetServiceTests --verbose
Test: kitchensink with RPi devices connected by USB -> Ethernet
adapters. Edit config.xml to try different tranport
overrides manually. Use dumpsys ethernet to show final
final network scores.
Change-Id: I482e78a76d06c9c090aa6816db14bb346eb6528b
3 files changed, 377 insertions, 34 deletions
diff --git a/java/com/android/server/ethernet/EthernetNetworkFactory.java b/java/com/android/server/ethernet/EthernetNetworkFactory.java index 54bcdc3..1f4f7fa 100644 --- a/java/com/android/server/ethernet/EthernetNetworkFactory.java +++ b/java/com/android/server/ethernet/EthernetNetworkFactory.java @@ -16,9 +16,11 @@ package com.android.server.ethernet; -import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.NonNull; import android.content.Context; +import android.net.ConnectivityManager; import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; @@ -37,10 +39,12 @@ import android.net.util.InterfaceParams; import android.os.Handler; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; import com.android.internal.util.IndentingPrintWriter; import java.io.FileDescriptor; +import java.lang.Math; import java.util.concurrent.ConcurrentHashMap; /** @@ -239,6 +243,52 @@ public class EthernetNetworkFactory extends NetworkFactory { private NetworkAgent mNetworkAgent; private IpConfiguration mIpConfig; + /** + * An object to contain all transport type information, including base network score and + * the legacy transport type it maps to (if any) + */ + private static class TransportInfo { + final int mLegacyType; + final int mScore; + + private TransportInfo(int legacyType, int score) { + mLegacyType = legacyType; + mScore = score; + } + } + + /** + * A map of TRANSPORT_* types to TransportInfo, making scoring and legacy type information + * available for each type an ethernet interface could propagate. + * + * Unfortunately, base scores for the various transports are not yet centrally located. + * They've been lifted from the corresponding NetworkFactory files in the meantime. + * + * Additionally, there are no legacy type equivalents to LOWPAN or WIFI_AWARE. These types + * are set to TYPE_NONE to match the behavior of their own network factories. + */ + private static final SparseArray<TransportInfo> sTransports = new SparseArray(); + static { + // LowpanInterfaceTracker.NETWORK_SCORE + sTransports.put(NetworkCapabilities.TRANSPORT_LOWPAN, + new TransportInfo(ConnectivityManager.TYPE_NONE, 30)); + // WifiAwareDataPathStateManager.NETWORK_FACTORY_SCORE_AVAIL + sTransports.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE, + new TransportInfo(ConnectivityManager.TYPE_NONE, 1)); + // EthernetNetworkFactory.NETWORK_SCORE + sTransports.put(NetworkCapabilities.TRANSPORT_ETHERNET, + new TransportInfo(ConnectivityManager.TYPE_ETHERNET, 70)); + // BluetoothTetheringNetworkFactory.NETWORK_SCORE + sTransports.put(NetworkCapabilities.TRANSPORT_BLUETOOTH, + new TransportInfo(ConnectivityManager.TYPE_BLUETOOTH, 69)); + // WifiNetworkFactory.SCORE_FILTER / NetworkAgent.WIFI_BASE_SCORE + sTransports.put(NetworkCapabilities.TRANSPORT_WIFI, + new TransportInfo(ConnectivityManager.TYPE_WIFI, 60)); + // TelephonyNetworkFactory.TELEPHONY_NETWORK_SCORE + sTransports.put(NetworkCapabilities.TRANSPORT_CELLULAR, + new TransportInfo(ConnectivityManager.TYPE_MOBILE, 50)); + } + long refCount = 0; private final IpClient.Callback mIpClientCallback = new IpClient.Callback() { @@ -259,14 +309,23 @@ public class EthernetNetworkFactory extends NetworkFactory { }; NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context, - NetworkCapabilities capabilities) { + @NonNull NetworkCapabilities capabilities) { name = ifaceName; - mCapabilities = capabilities; + mCapabilities = checkNotNull(capabilities); mHandler = handler; mContext = context; + int legacyType = ConnectivityManager.TYPE_NONE; + int[] transportTypes = mCapabilities.getTransportTypes(); + if (transportTypes.length > 0) { + legacyType = getLegacyType(transportTypes[0]); + } else { + // Should never happen as transport is always one of ETHERNET or a valid override + Log.w(TAG, "There is no transport type associated with network interface '" + + mLinkProperties.getInterfaceName() + "' -- Legacy type set to TYPE_NONE"); + } mHwAddress = hwAddress; - mNetworkInfo = new NetworkInfo(TYPE_ETHERNET, 0, NETWORK_TYPE, ""); + mNetworkInfo = new NetworkInfo(legacyType, 0, NETWORK_TYPE, ""); mNetworkInfo.setExtraInfo(mHwAddress); mNetworkInfo.setIsAvailable(true); } @@ -283,6 +342,47 @@ public class EthernetNetworkFactory extends NetworkFactory { return mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); } + /** + * Determines the legacy transport type from a NetworkCapabilities transport type. Defaults + * to legacy TYPE_NONE if there is no known conversion + */ + private static int getLegacyType(int transport) { + TransportInfo transportInfo = sTransports.get(transport, /* if dne */ null); + if (transportInfo != null) { + return transportInfo.mLegacyType; + } + return ConnectivityManager.TYPE_NONE; + } + + /** + * Determines the network score based on the transport associated with the interface. + * Ethernet interfaces could propagate a transport types forward. Since we can't + * get more information about the statuses of the interfaces on the other end of the local + * interface, we'll best-effort assign the score as the base score of the assigned transport + * when the link is up. When the link is down, the score is set to zero. + * + * This function is called with the purpose of assigning and updating the network score of + * the member NetworkAgent. + */ + private int getNetworkScore() { + // never set the network score below 0. + if (!mLinkUp) { + return 0; + } + + int[] transportTypes = mCapabilities.getTransportTypes(); + if (transportTypes.length < 1) { + Log.w(TAG, "There is no transport type associated with network interface '" + + mLinkProperties.getInterfaceName() + "' -- Score set to zero"); + return 0; + } + TransportInfo transportInfo = sTransports.get(transportTypes[0], /* if dne */ null); + if (transportInfo != null) { + return transportInfo.mScore; + } + return 0; + } + private void start() { if (mIpClient != null) { if (DBG) Log.d(TAG, "IpClient already started"); @@ -317,7 +417,7 @@ public class EthernetNetworkFactory extends NetworkFactory { // Create our NetworkAgent. mNetworkAgent = new NetworkAgent(mHandler.getLooper(), mContext, NETWORK_TYPE, mNetworkInfo, mCapabilities, mLinkProperties, - NETWORK_SCORE) { + getNetworkScore()) { public void unwanted() { if (this == mNetworkAgent) { stop(); @@ -390,8 +490,11 @@ public class EthernetNetworkFactory extends NetworkFactory { mNetworkAgent.sendNetworkCapabilities(mCapabilities); mNetworkAgent.sendNetworkInfo(mNetworkInfo); mNetworkAgent.sendLinkProperties(mLinkProperties); - // never set the network score below 0. - mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0); + + // As a note, getNetworkScore() is fairly expensive to calculate. This is fine for now + // since the agent isn't update frequently. Consider caching the score in the future if + // agent updating is required more often + mNetworkAgent.sendNetworkScore(getNetworkScore()); } private void clear() { @@ -433,7 +536,9 @@ public class EthernetNetworkFactory extends NetworkFactory { + "up: " + mLinkUp + ", " + "hwAddress: " + mHwAddress + ", " + "networkInfo: " + mNetworkInfo + ", " + + "networkCapabilities: " + mCapabilities + ", " + "networkAgent: " + mNetworkAgent + ", " + + "score: " + getNetworkScore() + ", " + "ipClient: " + mIpClient + "," + "linkProperties: " + mLinkProperties + "}"; diff --git a/java/com/android/server/ethernet/EthernetTracker.java b/java/com/android/server/ethernet/EthernetTracker.java index 00eedd5..099e054 100644 --- a/java/com/android/server/ethernet/EthernetTracker.java +++ b/java/com/android/server/ethernet/EthernetTracker.java @@ -294,12 +294,20 @@ final class EthernetTracker { } } + /** + * Parses an Ethernet interface configuration + * + * @param configString represents an Ethernet configuration in the following format: {@code + * <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]} + */ private void parseEthernetConfig(String configString) { - String[] tokens = configString.split(";"); + String[] tokens = configString.split(";", /* limit of tokens */ 4); String name = tokens[0]; String capabilities = tokens.length > 1 ? tokens[1] : null; + String transport = tokens.length > 3 ? tokens[3] : null; NetworkCapabilities nc = createNetworkCapabilities( - !TextUtils.isEmpty(capabilities) /* clear default capabilities */, capabilities); + !TextUtils.isEmpty(capabilities) /* clear default capabilities */, capabilities, + transport); mNetworkCapabilities.put(name, nc); if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) { @@ -320,24 +328,76 @@ final class EthernetTracker { } private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) { - return createNetworkCapabilities(clearDefaultCapabilities, null); + return createNetworkCapabilities(clearDefaultCapabilities, null, null); } - private static NetworkCapabilities createNetworkCapabilities( - boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities) { + /** + * Parses a static list of network capabilities + * + * @param clearDefaultCapabilities Indicates whether or not to clear any default capabilities + * @param commaSeparatedCapabilities A comma separated string list of integer encoded + * NetworkCapability.NET_CAPABILITY_* values + * @param overrideTransport A string representing a single override transport type, must be one + * of the NetworkCapability.TRANSPORT_* values. TRANSPORT_VPN is + * not supported. Errors with input will cause the override to + * be ignored. + */ + @VisibleForTesting + static NetworkCapabilities createNetworkCapabilities( + boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities, + @Nullable String overrideTransport) { NetworkCapabilities nc = new NetworkCapabilities(); if (clearDefaultCapabilities) { - nc.clearAll(); // Remove default capabilities. + nc.clearAll(); // Remove default capabilities and transports + } + + // Determine the transport type. If someone has tried to define an override transport then + // attempt to add it. Since we can only have one override, all errors with it will + // gracefully default back to TRANSPORT_ETHERNET and warn the user. VPN is not allowed as an + // override type. Wifi Aware and LoWPAN are currently unsupported as well. + int transport = NetworkCapabilities.TRANSPORT_ETHERNET; + if (!TextUtils.isEmpty(overrideTransport)) { + try { + int parsedTransport = Integer.valueOf(overrideTransport); + if (parsedTransport == NetworkCapabilities.TRANSPORT_VPN + || parsedTransport == NetworkCapabilities.TRANSPORT_WIFI_AWARE + || parsedTransport == NetworkCapabilities.TRANSPORT_LOWPAN) { + Log.e(TAG, "Override transport '" + parsedTransport + "' is not supported -- " + + "Defaulting to TRANSPORT_ETHERNET"); + } else { + transport = parsedTransport; + } + } catch (NumberFormatException nfe) { + Log.e(TAG, "Override transport type '" + overrideTransport + "' could not be parsed" + + "-- Defaulting to TRANSPORT_ETHERNET"); + } + } + + // Apply the transport. If the user supplied a valid number that is not a valid transport + // then adding will throw an exception. Default back to TRANSPORT_ETHERNET if that happens + try { + nc.addTransportType(transport); + } catch (IllegalArgumentException iae) { + Log.e(TAG, transport + " is not a valid NetworkCapability.TRANSPORT_* value" + + " -- Defaulting to TRANSPORT_ETHERNET"); + nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET); } - nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET); + nc.setLinkUpstreamBandwidthKbps(100 * 1000); nc.setLinkDownstreamBandwidthKbps(100 * 1000); if (!TextUtils.isEmpty(commaSeparatedCapabilities)) { for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) { if (!TextUtils.isEmpty(strNetworkCapability)) { - nc.addCapability(Integer.valueOf(strNetworkCapability)); + try { + nc.addCapability(Integer.valueOf(strNetworkCapability)); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Capability '" + strNetworkCapability + "' could not be parsed"); + } catch (IllegalArgumentException iae) { + Log.e(TAG, strNetworkCapability + " is not a valid " + + "NetworkCapability.NET_CAPABILITY_* value"); + } } } } diff --git a/tests/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/java/com/android/server/ethernet/EthernetTrackerTest.java index 70d316d..a56c9fe 100644 --- a/tests/java/com/android/server/ethernet/EthernetTrackerTest.java +++ b/tests/java/com/android/server/ethernet/EthernetTrackerTest.java @@ -23,6 +23,7 @@ import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkAddress; +import android.net.NetworkCapabilities; import android.net.StaticIpConfiguration; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -35,47 +36,55 @@ import java.net.InetAddress; @SmallTest @RunWith(AndroidJUnit4.class) public class EthernetTrackerTest { - + /** + * Test: Creation of various valid static IP configurations + */ @Test public void createStaticIpConfiguration() { - assertStaticConfiguration("", new StaticIpConfiguration()); + // Empty gives default StaticIPConfiguration object + assertStaticConfiguration(new StaticIpConfiguration(), ""); + // Setting only the IP address properly cascades and assumes defaults assertStaticConfiguration( - "ip=192.0.2.10/24", - new StaticIpConfigBuilder().setIp("192.0.2.10/24").build()); + new StaticIpConfigBuilder().setIp("192.0.2.10/24").build(), + "ip=192.0.2.10/24"); + // Setting other fields properly cascades them assertStaticConfiguration( - "ip=192.0.2.10/24 dns=4.4.4.4,8.8.8.8 gateway=192.0.2.1 domains=android", new StaticIpConfigBuilder() .setIp("192.0.2.10/24") .setDns(new String[] {"4.4.4.4", "8.8.8.8"}) .setGateway("192.0.2.1") .setDomains("android") - .build()); + .build(), + "ip=192.0.2.10/24 dns=4.4.4.4,8.8.8.8 gateway=192.0.2.1 domains=android"); // Verify order doesn't matter assertStaticConfiguration( - "domains=android ip=192.0.2.10/24 gateway=192.0.2.1 dns=4.4.4.4,8.8.8.8 ", new StaticIpConfigBuilder() .setIp("192.0.2.10/24") .setDns(new String[] {"4.4.4.4", "8.8.8.8"}) .setGateway("192.0.2.1") .setDomains("android") - .build()); + .build(), + "domains=android ip=192.0.2.10/24 gateway=192.0.2.1 dns=4.4.4.4,8.8.8.8 "); } + /** + * Test: Attempt creation of various bad static IP configurations + */ @Test public void createStaticIpConfiguration_Bad() { - assertFails("ip=192.0.2.1/24 gateway= blah=20.20.20.20"); // Unknown key - assertFails("ip=192.0.2.1"); // mask is missing - assertFails("ip=a.b.c"); // not a valid ip address - assertFails("dns=4.4.4.4,1.2.3.A"); // not valid ip address in dns - assertFails("="); // Key and value is empty - assertFails("ip="); // Value is empty - assertFails("ip=192.0.2.1/24 gateway="); // Gateway is empty + assertStaticConfigurationFails("ip=192.0.2.1/24 gateway= blah=20.20.20.20"); // Unknown key + assertStaticConfigurationFails("ip=192.0.2.1"); // mask is missing + assertStaticConfigurationFails("ip=a.b.c"); // not a valid ip address + assertStaticConfigurationFails("dns=4.4.4.4,1.2.3.A"); // not valid ip address in dns + assertStaticConfigurationFails("="); // Key and value is empty + assertStaticConfigurationFails("ip="); // Value is empty + assertStaticConfigurationFails("ip=192.0.2.1/24 gateway="); // Gateway is empty } - private void assertFails(String config) { + private void assertStaticConfigurationFails(String config) { try { EthernetTracker.parseStaticIpConfiguration(config); fail("Expected to fail: " + config); @@ -84,8 +93,8 @@ public class EthernetTrackerTest { } } - private void assertStaticConfiguration(String configAsString, - StaticIpConfiguration expectedStaticIpConfig) { + private void assertStaticConfiguration(StaticIpConfiguration expectedStaticIpConfig, + String configAsString) { IpConfiguration expectedIpConfiguration = new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE, expectedStaticIpConfig, null); @@ -118,9 +127,178 @@ public class EthernetTrackerTest { return this; } - StaticIpConfiguration build() { return new StaticIpConfiguration(config); } } + + /** + * Test: Attempt to create a capabilties with various valid sets of capabilities/transports + */ + @Test + public void createNetworkCapabilities() { + + // Particularly common expected results + NetworkCapabilities defaultEthernetCleared = new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(); + + NetworkCapabilities ethernetClearedWithCommonCaps = new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(12) + .addCapability(13) + .addCapability(14) + .addCapability(15) + .build(); + + // Empty capabilities and transports lists with a "please clear defaults" should + // yield an empty capabilities set with TRANPORT_ETHERNET + assertNetworkCapabilities(defaultEthernetCleared, true, "", ""); + + // Empty capabilities and transports without the clear defaults flag should return the + // default capabilities set with TRANSPORT_ETHERNET + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(), + false, "", ""); + + // A list of capabilities without the clear defaults flag should return the default + // capabilities, mixed with the desired capabilities, and TRANSPORT_ETHERNET + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(11) + .addCapability(12) + .build(), + false, "11,12", ""); + + // Adding a list of capabilities with a clear defaults will leave exactly those capabilities + // with a default TRANSPORT_ETHERNET since no overrides are specified + assertNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15", ""); + + // Adding any invalid capabilities to the list will cause them to be ignored + assertNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,65,73", ""); + assertNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,abcdefg", ""); + + // Adding a valid override transport will remove the default TRANSPORT_ETHERNET transport + // and apply only the override to the capabiltities object + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(0) + .build(), + true, "", "0"); + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(1) + .build(), + true, "", "1"); + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(2) + .build(), + true, "", "2"); + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addTransport(3) + .build(), + true, "", "3"); + + // "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANPORT_ETHERNET + assertNetworkCapabilities(defaultEthernetCleared, true, "", "4"); + + // "5" is TRANSPORT_WIFI_AWARE, which is currently supported due to no legacy TYPE_NONE + // conversion. When that becomes available, this test must be updated + assertNetworkCapabilities(defaultEthernetCleared, true, "", "5"); + + // "6" is TRANSPORT_LOWPAN, which is currently supported due to no legacy TYPE_NONE + // conversion. When that becomes available, this test must be updated + assertNetworkCapabilities(defaultEthernetCleared, true, "", "6"); + + // Adding an invalid override transport will leave the transport as TRANSPORT_ETHERNET + assertNetworkCapabilities(defaultEthernetCleared,true, "", "100"); + assertNetworkCapabilities(defaultEthernetCleared, true, "", "abcdefg"); + + // Ensure the adding of both capabilities and transports work + assertNetworkCapabilities( + new NetworkCapabilitiesBuilder() + .clearAll() + .setLinkUpstreamBandwidthKbps(100000) + .setLinkDownstreamBandwidthKbps(100000) + .addCapability(12) + .addCapability(13) + .addCapability(14) + .addCapability(15) + .addTransport(3) + .build(), + true, "12,13,14,15", "3"); + + // Ensure order does not matter for capability list + assertNetworkCapabilities(ethernetClearedWithCommonCaps, true, "13,12,15,14", ""); + } + + private void assertNetworkCapabilities(NetworkCapabilities expectedNetworkCapabilities, + boolean clearCapabilties, String configCapabiltiies,String configTransports) { + assertEquals(expectedNetworkCapabilities, + EthernetTracker.createNetworkCapabilities(clearCapabilties, configCapabiltiies, + configTransports)); + } + + private static class NetworkCapabilitiesBuilder { + private final NetworkCapabilities nc = new NetworkCapabilities(); + + NetworkCapabilitiesBuilder clearAll(){ + // This is THE ONLY one that doesn't return a reference to the object so I wrapped + // everything in a builder to keep things consistent and clean above. Fix if this + // ever changes + nc.clearAll(); + return this; + } + + NetworkCapabilitiesBuilder addCapability(int capability) { + nc.addCapability(capability); + return this; + } + + NetworkCapabilitiesBuilder addTransport(int transport) { + nc.addTransportType(transport); + return this; + } + + NetworkCapabilitiesBuilder setLinkUpstreamBandwidthKbps(int upKbps) { + nc.setLinkUpstreamBandwidthKbps(upKbps); + return this; + } + + NetworkCapabilitiesBuilder setLinkDownstreamBandwidthKbps(int downKbps) { + nc.setLinkDownstreamBandwidthKbps(downKbps); + return this; + } + + NetworkCapabilities build() { + return new NetworkCapabilities(nc); + } + } } |