Merge changes I8e806b3b,I5e8e4673,Id2a078da

* changes:
  Add CTS tests for EthernetNetworkProvider changes
  Move EthernetNetworkFactory to using the NetworkProvider API
  Improve waiting for interface added or removed
This commit is contained in:
Patrick Rohr
2022-05-24 03:34:25 +00:00
committed by Gerrit Code Review
3 changed files with 217 additions and 181 deletions

View File

@@ -31,10 +31,9 @@ import android.net.IpConfiguration.ProxySettings;
import android.net.LinkProperties;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.NetworkScore;
import android.net.ip.IIpClient;
import android.net.ip.IpClientCallbacks;
import android.net.ip.IpClientManager;
@@ -61,22 +60,19 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* {@link NetworkFactory} that represents Ethernet networks.
*
* This class reports a static network score of 70 when it is tracking an interface and that
* interface's link is up, and a score of 0 otherwise.
* {@link NetworkProvider} that manages NetworkOffers for Ethernet networks.
*/
public class EthernetNetworkFactory extends NetworkFactory {
public class EthernetNetworkFactory {
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
final static boolean DBG = true;
private final static int NETWORK_SCORE = 70;
private static final String NETWORK_TYPE = "Ethernet";
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
new ConcurrentHashMap<>();
private final Handler mHandler;
private final Context mContext;
private final NetworkProvider mProvider;
final Dependencies mDeps;
public static class Dependencies {
@@ -111,54 +107,24 @@ public class EthernetNetworkFactory extends NetworkFactory {
}
public EthernetNetworkFactory(Handler handler, Context context) {
this(handler, context, new Dependencies());
this(handler, context, new NetworkProvider(context, handler.getLooper(), TAG),
new Dependencies());
}
@VisibleForTesting
EthernetNetworkFactory(Handler handler, Context context, Dependencies deps) {
super(handler.getLooper(), context, NETWORK_TYPE, createDefaultNetworkCapabilities());
EthernetNetworkFactory(Handler handler, Context context, NetworkProvider provider,
Dependencies deps) {
mHandler = handler;
mContext = context;
mProvider = provider;
mDeps = deps;
setScoreFilter(NETWORK_SCORE);
}
@Override
public boolean acceptRequest(NetworkRequest request) {
if (DBG) {
Log.d(TAG, "acceptRequest, request: " + request);
}
return networkForRequest(request) != null;
}
@Override
protected void needNetworkFor(NetworkRequest networkRequest) {
NetworkInterfaceState network = networkForRequest(networkRequest);
if (network == null) {
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
return;
}
if (++network.refCount == 1) {
network.start();
}
}
@Override
protected void releaseNetworkFor(NetworkRequest networkRequest) {
NetworkInterfaceState network = networkForRequest(networkRequest);
if (network == null) {
Log.e(TAG, "releaseNetworkFor, failed to get a network for " + networkRequest);
return;
}
if (--network.refCount == 0) {
network.stop();
}
/**
* Registers the network provider with the system.
*/
public void register() {
mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
}
/**
@@ -196,9 +162,8 @@ public class EthernetNetworkFactory extends NetworkFactory {
}
final NetworkInterfaceState iface = new NetworkInterfaceState(
ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, getProvider(), mDeps);
ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, mProvider, mDeps);
mTrackingInterfaces.put(ifaceName, iface);
updateCapabilityFilter();
}
@VisibleForTesting
@@ -239,7 +204,6 @@ public class EthernetNetworkFactory extends NetworkFactory {
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
iface.updateInterface(ipConfig, capabilities, listener);
mTrackingInterfaces.put(ifaceName, iface);
updateCapabilityFilter();
}
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
@@ -250,16 +214,6 @@ public class EthernetNetworkFactory extends NetworkFactory {
return builder.build();
}
private void updateCapabilityFilter() {
NetworkCapabilities capabilitiesFilter = createDefaultNetworkCapabilities();
for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
capabilitiesFilter = mixInCapabilities(capabilitiesFilter, iface.mCapabilities);
}
if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
setCapabilityFilter(capabilitiesFilter);
}
private static NetworkCapabilities createDefaultNetworkCapabilities() {
return NetworkCapabilities.Builder
.withoutDefaultCapabilities()
@@ -270,11 +224,8 @@ public class EthernetNetworkFactory extends NetworkFactory {
protected void removeInterface(String interfaceName) {
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
if (iface != null) {
iface.maybeSendNetworkManagementCallbackForAbort();
iface.stop();
iface.destroy();
}
updateCapabilityFilter();
}
/** Returns true if state has been modified */
@@ -306,37 +257,6 @@ public class EthernetNetworkFactory extends NetworkFactory {
return mTrackingInterfaces.containsKey(ifaceName);
}
private NetworkInterfaceState networkForRequest(NetworkRequest request) {
String requestedIface = null;
NetworkSpecifier specifier = request.getNetworkSpecifier();
if (specifier instanceof EthernetNetworkSpecifier) {
requestedIface = ((EthernetNetworkSpecifier) specifier)
.getInterfaceName();
}
NetworkInterfaceState network = null;
if (!TextUtils.isEmpty(requestedIface)) {
NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
if (n != null && request.canBeSatisfiedBy(n.mCapabilities)) {
network = n;
}
} else {
for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
if (request.canBeSatisfiedBy(n.mCapabilities) && n.mLinkUp) {
network = n;
break;
}
}
}
if (DBG) {
Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
}
return network;
}
private static void maybeSendNetworkManagementCallback(
@Nullable final INetworkInterfaceOutcomeReceiver listener,
@Nullable final String iface,
@@ -401,8 +321,6 @@ public class EthernetNetworkFactory extends NetworkFactory {
ConnectivityManager.TYPE_NONE);
}
long refCount = 0;
private class EthernetIpClientCallback extends IpClientCallbacks {
private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
@@ -476,6 +394,9 @@ public class EthernetNetworkFactory extends NetworkFactory {
private class EthernetNetworkOfferCallback implements NetworkProvider.NetworkOfferCallback {
@Override
public void onNetworkNeeded(@NonNull NetworkRequest request) {
if (DBG) {
Log.d(TAG, String.format("%s: onNetworkNeeded for request: %s", name, request));
}
// When the network offer is first registered, onNetworkNeeded is called with all
// existing requests.
// ConnectivityService filters requests for us based on the NetworkCapabilities
@@ -487,6 +408,10 @@ public class EthernetNetworkFactory extends NetworkFactory {
@Override
public void onNetworkUnneeded(@NonNull NetworkRequest request) {
if (DBG) {
Log.d(TAG,
String.format("%s: onNetworkUnneeded for request: %s", name, request));
}
mRequests.remove(request);
if (mRequests.isEmpty()) {
// not currently serving any requests, stop the network.
@@ -529,9 +454,21 @@ public class EthernetNetworkFactory extends NetworkFactory {
+ "transport type.");
}
private static NetworkScore getBestNetworkScore() {
return new NetworkScore.Builder().build();
}
private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
mCapabilities = new NetworkCapabilities(capabilities);
mLegacyType = getLegacyType(mCapabilities);
if (mLinkUp) {
// registering a new network offer will update the existing one, not install a
// new one.
mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
new NetworkCapabilities(capabilities), cmd -> mHandler.post(cmd),
mNetworkOfferCallback);
}
}
void updateInterface(@Nullable final IpConfiguration ipConfig,
@@ -693,20 +630,21 @@ public class EthernetNetworkFactory extends NetworkFactory {
mLinkUp = up;
if (!up) { // was up, goes down
// Send an abort on a provisioning request callback if necessary before stopping.
maybeSendNetworkManagementCallbackForAbort();
stop();
// retract network offer and stop IpClient.
destroy();
// If only setting the interface down, send a callback to signal completion.
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
} else { // was down, goes up
stop();
start(listener);
// register network offer
mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
new NetworkCapabilities(mCapabilities), (cmd) -> mHandler.post(cmd),
mNetworkOfferCallback);
}
return true;
}
void stop() {
private void stop() {
// Invalidate all previous start requests
if (mIpClient != null) {
mIpClient.shutdown();
@@ -722,6 +660,13 @@ public class EthernetNetworkFactory extends NetworkFactory {
mLinkProperties.clear();
}
public void destroy() {
mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
maybeSendNetworkManagementCallbackForAbort();
stop();
mRequests.clear();
}
private static void provisionIpClient(@NonNull final IpClientManager ipClient,
@NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
if (config.getProxySettings() == ProxySettings.STATIC ||
@@ -761,7 +706,6 @@ public class EthernetNetworkFactory extends NetworkFactory {
@Override
public String toString() {
return getClass().getSimpleName() + "{ "
+ "refCount: " + refCount + ", "
+ "iface: " + name + ", "
+ "up: " + mLinkUp + ", "
+ "hwAddress: " + mHwAddress + ", "
@@ -774,7 +718,6 @@ public class EthernetNetworkFactory extends NetworkFactory {
}
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
super.dump(fd, pw, args);
pw.println(getClass().getSimpleName());
pw.println("Tracking interfaces:");
pw.increaseIndent();

View File

@@ -19,9 +19,16 @@ import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
import android.content.Context
import android.net.ConnectivityManager
import android.net.EthernetNetworkSpecifier
import android.net.InetAddresses
import android.net.IpConfiguration
import android.net.MacAddress
import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkRequest
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
@@ -41,10 +48,14 @@ import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
import com.android.testutils.anyNetwork
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RouterAdvertisementResponder
import com.android.testutils.SC_V2
import com.android.testutils.TapPacketReader
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
import org.junit.After
@@ -64,6 +75,11 @@ private const val TIMEOUT_MS = 1000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
private val ETH_REQUEST: NetworkRequest = NetworkRequest.Builder()
.addTransportType(TRANSPORT_TEST)
.addTransportType(TRANSPORT_ETHERNET)
.removeCapability(NET_CAPABILITY_TRUSTED)
.build()
@AppModeFull(reason = "Instant apps can't access EthernetManager")
@RunWith(AndroidJUnit4::class)
@@ -74,10 +90,12 @@ class EthernetManagerTest {
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
private val ifaceListener = EthernetStateListener()
private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<EthernetStateListener>()
private val networkRequests = ArrayList<TestableNetworkCallback>()
private class EthernetTestInterface(
context: Context,
@@ -177,10 +195,12 @@ class EthernetManagerTest {
setIncludeTestInterfaces(false)
for (iface in createdIfaces) {
iface.destroy()
ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
for (listener in addedListeners) {
em.removeInterfaceStateListener(listener)
}
networkRequests.forEach { cm.unregisterNetworkCallback(it) }
}
private fun addInterfaceStateListener(listener: EthernetStateListener) {
@@ -195,7 +215,11 @@ class EthernetManagerTest {
context,
Handler(Looper.getMainLooper())
).also { createdIfaces.add(it) }
ifaceListener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
with(ifaceListener) {
// when an interface comes up, we should always see a down cb before an up cb.
eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
return iface
}
@@ -208,8 +232,37 @@ class EthernetManagerTest {
private fun removeInterface(iface: EthernetTestInterface) {
iface.destroy()
createdIfaces.remove(iface)
ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
return TestableNetworkCallback().also {
cm.requestNetwork(request, it)
networkRequests.add(it)
}
}
private fun releaseNetwork(cb: TestableNetworkCallback) {
cm.unregisterNetworkCallback(cb)
networkRequests.remove(cb)
}
private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
.setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
// It can take multiple seconds for the network to become available.
private fun TestableNetworkCallback.expectAvailable() =
expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network
// b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
// eventuallyExpect(Lost::class) instead.
private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
private fun TestableNetworkCallback.assertNotLost(n: Network? = null) =
assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
@Test
fun testCallbacks() {
// If an interface exists when the callback is registered, it is reported on registration.
@@ -289,4 +342,105 @@ class EthernetManagerTest {
removeInterface(iface2)
}
@Test
fun testNetworkRequest_withSingleExistingInterface() {
setIncludeTestInterfaces(true)
createInterface()
// install a listener which will later be used to verify the Lost callback
val listenerCb = TestableNetworkCallback()
cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
networkRequests.add(listenerCb)
val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable()
cb.assertNotLost()
releaseNetwork(cb)
listenerCb.eventuallyExpectLost(network)
}
@Test
fun testNetworkRequest_beforeSingleInterfaceIsUp() {
setIncludeTestInterfaces(true)
val cb = requestNetwork(ETH_REQUEST)
// bring up interface after network has been requested
val iface = createInterface()
val network = cb.expectAvailable()
// remove interface before network request has been removed
cb.assertNotLost()
removeInterface(iface)
cb.eventuallyExpectLost()
releaseNetwork(cb)
}
@Test
fun testNetworkRequest_withMultipleInterfaces() {
setIncludeTestInterfaces(true)
val iface1 = createInterface()
val iface2 = createInterface()
val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
val network = cb.expectAvailable()
cb.expectCapabilitiesThat(network) {
it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
}
removeInterface(iface1)
cb.assertNotLost()
removeInterface(iface2)
cb.eventuallyExpectLost()
releaseNetwork(cb)
}
@Test
fun testNetworkRequest_withInterfaceBeingReplaced() {
setIncludeTestInterfaces(true)
val iface1 = createInterface()
val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable()
// create another network and verify the request sticks to the current network
val iface2 = createInterface()
cb.assertNotLost()
// remove iface1 and verify the request brings up iface2
removeInterface(iface1)
cb.eventuallyExpectLost(network)
val network2 = cb.expectAvailable()
releaseNetwork(cb)
}
@Test
fun testNetworkRequest_withMultipleInterfacesAndRequests() {
setIncludeTestInterfaces(true)
val iface1 = createInterface()
val iface2 = createInterface()
val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName))
val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
val cb3 = requestNetwork(ETH_REQUEST)
cb1.expectAvailable()
cb2.expectAvailable()
cb3.expectAvailable()
cb1.assertNotLost()
cb2.assertNotLost()
cb3.assertNotLost()
releaseNetwork(cb1)
releaseNetwork(cb2)
releaseNetwork(cb3)
}
}

View File

@@ -32,7 +32,6 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,6 +50,7 @@ import android.net.Network;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.NetworkProvider.NetworkOfferCallback;
import android.net.NetworkRequest;
import android.net.StaticIpConfiguration;
import android.net.ip.IpClientCallbacks;
@@ -99,6 +99,7 @@ public class EthernetNetworkFactoryTest {
@Mock private EthernetNetworkAgent mNetworkAgent;
@Mock private InterfaceParams mInterfaceParams;
@Mock private Network mMockNetwork;
@Mock private NetworkProvider mNetworkProvider;
@Before
public void setUp() throws Exception {
@@ -112,7 +113,7 @@ public class EthernetNetworkFactoryTest {
private void initEthernetNetworkFactory() {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps);
mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mNetworkProvider, mDeps);
}
private void setupNetworkAgentMock() {
@@ -239,9 +240,16 @@ public class EthernetNetworkFactoryTest {
mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
createInterfaceCapsBuilder(transportType).build());
assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
ArgumentCaptor<NetworkOfferCallback> captor = ArgumentCaptor.forClass(
NetworkOfferCallback.class);
verify(mNetworkProvider).registerNetworkOffer(any(), any(), any(), captor.capture());
captor.getValue().onNetworkNeeded(createDefaultRequest());
verifyStart(ipConfig);
clearInvocations(mDeps);
clearInvocations(mIpClient);
clearInvocations(mNetworkProvider);
}
// creates a provisioned interface
@@ -281,28 +289,14 @@ public class EthernetNetworkFactoryTest {
// To create an unprovisioned interface, provision and then "stop" it, i.e. stop its
// NetworkAgent and IpClient. One way this can be done is by provisioning an interface and
// then calling onNetworkUnwanted.
createAndVerifyProvisionedInterface(iface);
mNetworkAgent.getCallbacks().onNetworkUnwanted();
mLooper.dispatchAll();
verifyStop();
mNetFactory.addInterface(iface, HW_ADDR, createDefaultIpConfig(),
createInterfaceCapsBuilder(NetworkCapabilities.TRANSPORT_ETHERNET).build());
assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
clearInvocations(mIpClient);
clearInvocations(mNetworkAgent);
}
@Test
public void testAcceptRequest() throws Exception {
initEthernetNetworkFactory();
createInterfaceUndergoingProvisioning(TEST_IFACE);
assertTrue(mNetFactory.acceptRequest(createDefaultRequest()));
NetworkRequest wifiRequest = createDefaultRequestBuilder()
.removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
assertFalse(mNetFactory.acceptRequest(wifiRequest));
}
@Test
public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
initEthernetNetworkFactory();
@@ -377,36 +371,6 @@ public class EthernetNetworkFactoryTest {
listener.expectOnError();
}
@Test
public void testNeedNetworkForOnProvisionedInterface() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
mNetFactory.needNetworkFor(createDefaultRequest());
verify(mIpClient, never()).startProvisioning(any());
}
@Test
public void testNeedNetworkForOnUnprovisionedInterface() throws Exception {
initEthernetNetworkFactory();
createUnprovisionedInterface(TEST_IFACE);
mNetFactory.needNetworkFor(createDefaultRequest());
verify(mIpClient).startProvisioning(any());
triggerOnProvisioningSuccess();
verifyNetworkAgentRegistersAndConnects();
}
@Test
public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception {
initEthernetNetworkFactory();
createInterfaceUndergoingProvisioning(TEST_IFACE);
mNetFactory.needNetworkFor(createDefaultRequest());
verify(mIpClient, never()).startProvisioning(any());
triggerOnProvisioningSuccess();
verifyNetworkAgentRegistersAndConnects();
}
@Test
public void testProvisioningLoss() throws Exception {
initEthernetNetworkFactory();
@@ -440,31 +404,6 @@ public class EthernetNetworkFactoryTest {
verify(mIpClient, never()).startProvisioning(any());
}
@Test
public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception {
initEthernetNetworkFactory();
createUnprovisionedInterface(TEST_IFACE);
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER);
mNetFactory.needNetworkFor(createDefaultRequest());
verify(mDeps, never()).makeIpClient(any(), any(), any());
// BUG(b/191854824): requesting a network with a specifier (Android Auto use case) should
// not start an IpClient when the link is down, but fixing this may make matters worse by
// tiggering b/197548738.
NetworkRequest specificNetRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE))
.build();
mNetFactory.needNetworkFor(specificNetRequest);
mNetFactory.releaseNetworkFor(specificNetRequest);
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER);
// TODO: change to once when b/191854824 is fixed.
verify(mDeps, times(2)).makeIpClient(any(), eq(TEST_IFACE), any());
}
@Test
public void testLinkPropertiesChanged() throws Exception {
initEthernetNetworkFactory();