diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java index 7b727a3ce3..c610f00ab1 100644 --- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java +++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java @@ -22,10 +22,12 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.EthernetNetworkSpecifier; import android.net.IInternalNetworkManagementListener; +import android.net.InternalNetworkManagementException; import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkFactory; @@ -203,14 +205,9 @@ public class EthernetNetworkFactory extends NetworkFactory { @Nullable final IInternalNetworkManagementListener listener) { enforceInterfaceIsTracked(ifaceName); final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName); - // TODO: The listener will have issues if called in quick succession for the same interface - // before the IP layer restarts. Update the listener logic to address multiple successive - // calls for a particular interface. - iface.mNetworkManagementListener = listener; - if (iface.updateInterface(ipConfig, capabilities)) { - mTrackingInterfaces.put(ifaceName, iface); - updateCapabilityFilter(); - } + iface.updateInterface(ipConfig, capabilities, listener); + mTrackingInterfaces.put(ifaceName, iface); + updateCapabilityFilter(); } private void enforceInterfaceIsTracked(@NonNull final String ifaceName) { @@ -247,6 +244,7 @@ public class EthernetNetworkFactory extends NetworkFactory { void removeInterface(String interfaceName) { NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName); if (iface != null) { + iface.maybeSendNetworkManagementCallbackForAbort(); iface.stop(); } @@ -302,6 +300,21 @@ public class EthernetNetworkFactory extends NetworkFactory { return network; } + private static void maybeSendNetworkManagementCallback( + @Nullable final IInternalNetworkManagementListener listener, + @Nullable final Network network, + @Nullable final InternalNetworkManagementException e) { + if (null == listener) { + return; + } + + try { + listener.onComplete(network, e); + } catch (RemoteException re) { + Log.e(TAG, "Can't send onComplete for network management callback", re); + } + } + @VisibleForTesting static class NetworkInterfaceState { final String name; @@ -320,8 +333,7 @@ public class EthernetNetworkFactory extends NetworkFactory { private volatile @Nullable IpClientManager mIpClient; private @NonNull NetworkCapabilities mCapabilities; - private @Nullable IpClientCallbacksImpl mIpClientCallback; - private @Nullable IInternalNetworkManagementListener mNetworkManagementListener; + private @Nullable EthernetIpClientCallback mIpClientCallback; private @Nullable EthernetNetworkAgent mNetworkAgent; private @Nullable IpConfiguration mIpConfig; @@ -348,9 +360,14 @@ public class EthernetNetworkFactory extends NetworkFactory { long refCount = 0; - private class IpClientCallbacksImpl extends IpClientCallbacks { + private class EthernetIpClientCallback extends IpClientCallbacks { private final ConditionVariable mIpClientStartCv = new ConditionVariable(false); private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false); + @Nullable IInternalNetworkManagementListener mNetworkManagementListener; + + EthernetIpClientCallback(@Nullable final IInternalNetworkManagementListener listener) { + mNetworkManagementListener = listener; + } @Override public void onIpClientCreated(IIpClient ipClient) { @@ -368,12 +385,12 @@ public class EthernetNetworkFactory extends NetworkFactory { @Override public void onProvisioningSuccess(LinkProperties newLp) { - mHandler.post(() -> onIpLayerStarted(newLp)); + mHandler.post(() -> onIpLayerStarted(newLp, mNetworkManagementListener)); } @Override public void onProvisioningFailure(LinkProperties newLp) { - mHandler.post(() -> onIpLayerStopped(newLp)); + mHandler.post(() -> onIpLayerStopped(mNetworkManagementListener)); } @Override @@ -431,30 +448,25 @@ public class EthernetNetworkFactory extends NetworkFactory { mLegacyType = getLegacyType(mCapabilities); } - boolean updateInterface(@NonNull final IpConfiguration ipConfig, - @Nullable final NetworkCapabilities capabilities) { - final boolean shouldUpdateIpConfig = !Objects.equals(mIpConfig, ipConfig); - final boolean shouldUpdateCapabilities = null != capabilities - && !Objects.equals(mCapabilities, capabilities); + void updateInterface(@NonNull final IpConfiguration ipConfig, + @Nullable final NetworkCapabilities capabilities, + @Nullable final IInternalNetworkManagementListener listener) { if (DBG) { Log.d(TAG, "updateInterface, iface: " + name - + ", shouldUpdateIpConfig: " + shouldUpdateIpConfig - + ", shouldUpdateCapabilities: " + shouldUpdateCapabilities + ", ipConfig: " + ipConfig + ", old ipConfig: " + mIpConfig + ", capabilities: " + capabilities + ", old capabilities: " + mCapabilities + + ", listener: " + listener ); } - if (shouldUpdateIpConfig) { mIpConfig = ipConfig; }; - if (shouldUpdateCapabilities) { setCapabilities(capabilities); }; - if (shouldUpdateIpConfig || shouldUpdateCapabilities) { - // TODO: Update this logic to only do a restart if required. Although a restart may - // be required due to the capabilities or ipConfiguration values, not all - // capabilities changes require a restart. - restart(); - return true; - } - return false; + mIpConfig = ipConfig; + setCapabilities(capabilities); + // Send an abort callback if a request is filed before the previous one has completed. + maybeSendNetworkManagementCallbackForAbort(); + // TODO: Update this logic to only do a restart if required. Although a restart may + // be required due to the capabilities or ipConfiguration values, not all + // capabilities changes require a restart. + restart(listener); } boolean isRestricted() { @@ -462,6 +474,10 @@ public class EthernetNetworkFactory extends NetworkFactory { } private void start() { + start(null); + } + + private void start(@Nullable final IInternalNetworkManagementListener listener) { if (mIpClient != null) { if (DBG) Log.d(TAG, "IpClient already started"); return; @@ -470,9 +486,10 @@ public class EthernetNetworkFactory extends NetworkFactory { Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name)); } - mIpClientCallback = new IpClientCallbacksImpl(); + mIpClientCallback = new EthernetIpClientCallback(listener); mDeps.makeIpClient(mContext, name, mIpClientCallback); mIpClientCallback.awaitIpClientStart(); + if (sTcpBufferSizes == null) { sTcpBufferSizes = mContext.getResources().getString( com.android.internal.R.string.config_ethernet_tcp_buffers); @@ -480,8 +497,13 @@ public class EthernetNetworkFactory extends NetworkFactory { provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes); } - void onIpLayerStarted(LinkProperties linkProperties) { + void onIpLayerStarted(@NonNull final LinkProperties linkProperties, + @Nullable final IInternalNetworkManagementListener listener) { if(mIpClient == null) { + // This call comes from a message posted on the handler thread, but the IpClient has + // since been stopped such as may be the case if updateInterfaceLinkState() is + // queued on the handler thread prior. As network management callbacks are sent in + // stop(), there is no need to send them again here. if (DBG) Log.d(TAG, "IpClient is not initialized."); return; } @@ -516,33 +538,53 @@ public class EthernetNetworkFactory extends NetworkFactory { }); mNetworkAgent.register(); mNetworkAgent.markConnected(); - sendNetworkManagementCallback(); + realizeNetworkManagementCallback(mNetworkAgent.getNetwork(), null); } - private void sendNetworkManagementCallback() { - if (null != mNetworkManagementListener) { - try { - mNetworkManagementListener.onComplete(mNetworkAgent.getNetwork(), null); - } catch (RemoteException e) { - Log.e(TAG, "Can't send onComplete for network management callback", e); - } finally { - mNetworkManagementListener = null; - } - } - } - - void onIpLayerStopped(LinkProperties linkProperties) { + void onIpLayerStopped(@Nullable final IInternalNetworkManagementListener listener) { + // This cannot happen due to provisioning timeout, because our timeout is 0. It can + // happen due to errors while provisioning or on provisioning loss. if(mIpClient == null) { if (DBG) Log.d(TAG, "IpClient is not initialized."); return; } - // This cannot happen due to provisioning timeout, because our timeout is 0. It can only - // happen if we're provisioned and we lose provisioning. - stop(); - // If the interface has disappeared provisioning will fail over and over again, so - // there is no point in starting again - if (null != mDeps.getNetworkInterfaceByName(name)) { - start(); + // There is no point in continuing if the interface is gone as stop() will be triggered + // by removeInterface() when processed on the handler thread and start() won't + // work for a non-existent interface. + if (null == mDeps.getNetworkInterfaceByName(name)) { + if (DBG) Log.d(TAG, name + " is no longer available."); + // Send a callback in case a provisioning request was in progress. + maybeSendNetworkManagementCallbackForAbort(); + return; + } + restart(listener); + } + + private void maybeSendNetworkManagementCallbackForAbort() { + realizeNetworkManagementCallback(null, + new InternalNetworkManagementException( + "The IP provisioning request has been aborted.")); + } + + // Must be called on the handler thread + private void realizeNetworkManagementCallback(@Nullable final Network network, + @Nullable final InternalNetworkManagementException e) { + ensureRunningOnEthernetHandlerThread(); + if (null == mIpClientCallback) { + return; + } + + EthernetNetworkFactory.maybeSendNetworkManagementCallback( + mIpClientCallback.mNetworkManagementListener, network, e); + // Only send a single callback per listener. + mIpClientCallback.mNetworkManagementListener = null; + } + + private void ensureRunningOnEthernetHandlerThread() { + if (mHandler.getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException( + "Not running on the Ethernet thread: " + + Thread.currentThread().getName()); } } @@ -577,8 +619,11 @@ public class EthernetNetworkFactory extends NetworkFactory { if (mLinkUp == up) return false; mLinkUp = up; - stop(); - if (up) { + if (!up) { // was up, goes down + maybeSendNetworkManagementCallbackForAbort(); + stop(); + } else { // was down, goes up + stop(); start(); } @@ -627,10 +672,14 @@ public class EthernetNetworkFactory extends NetworkFactory { .build(); } - void restart(){ + void restart() { + restart(null); + } + + void restart(@Nullable final IInternalNetworkManagementListener listener){ if (DBG) Log.d(TAG, "reconnecting Ethernet"); stop(); - start(); + start(listener); } @Override diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java index 52ddf3c8ad..d569990a18 100644 --- a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java +++ b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java @@ -16,8 +16,11 @@ package com.android.server.ethernet; +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -63,6 +66,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.net.module.util.InterfaceParams; +import com.android.testutils.DevSdkIgnoreRule; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -72,9 +77,7 @@ import org.mockito.MockitoAnnotations; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; @RunWith(AndroidJUnit4.class) @SmallTest @@ -84,7 +87,7 @@ public class EthernetNetworkFactoryTest { private static final String IP_ADDR = "192.0.2.2/25"; private static final LinkAddress LINK_ADDR = new LinkAddress(IP_ADDR); private static final String HW_ADDR = "01:02:03:04:05:06"; - private final TestLooper mLooper = new TestLooper(); + private TestLooper mLooper; private Handler mHandler; private EthernetNetworkFactory mNetFactory = null; private IpClientCallbacks mIpClientCallbacks; @@ -99,14 +102,18 @@ public class EthernetNetworkFactoryTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mHandler = new Handler(mLooper.getLooper()); - mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps); - setupNetworkAgentMock(); setupIpClientMock(); setupContext(); } + //TODO: Move away from usage of TestLooper in order to move this logic back into @Before. + private void initEthernetNetworkFactory() { + mLooper = new TestLooper(); + mHandler = new Handler(mLooper.getLooper()); + mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps); + } + private void setupNetworkAgentMock() { when(mDeps.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any())) .thenAnswer(new AnswerWithArguments() { @@ -288,6 +295,7 @@ public class EthernetNetworkFactoryTest { @Test public void testAcceptRequest() throws Exception { + initEthernetNetworkFactory(); createInterfaceUndergoingProvisioning(TEST_IFACE); assertTrue(mNetFactory.acceptRequest(createDefaultRequest())); @@ -299,6 +307,7 @@ public class EthernetNetworkFactoryTest { @Test public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception { + initEthernetNetworkFactory(); createInterfaceUndergoingProvisioning(TEST_IFACE); // verify that the IpClient gets shut down when interface state changes to down. assertTrue(mNetFactory.updateInterfaceLinkState(TEST_IFACE, false)); @@ -307,6 +316,7 @@ public class EthernetNetworkFactoryTest { @Test public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); assertTrue(mNetFactory.updateInterfaceLinkState(TEST_IFACE, false)); verifyStop(); @@ -314,6 +324,7 @@ public class EthernetNetworkFactoryTest { @Test public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception { + initEthernetNetworkFactory(); createUnprovisionedInterface(TEST_IFACE); assertTrue(mNetFactory.updateInterfaceLinkState(TEST_IFACE, false)); // There should not be an active IPClient or NetworkAgent. @@ -324,6 +335,7 @@ public class EthernetNetworkFactoryTest { @Test public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception { + initEthernetNetworkFactory(); // if interface was never added, link state cannot be updated. assertFalse(mNetFactory.updateInterfaceLinkState("eth1", true)); verify(mDeps, never()).makeIpClient(any(), any(), any()); @@ -331,6 +343,7 @@ public class EthernetNetworkFactoryTest { @Test public void testNeedNetworkForOnProvisionedInterface() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); mNetFactory.needNetworkFor(createDefaultRequest()); verify(mIpClient, never()).startProvisioning(any()); @@ -338,6 +351,7 @@ public class EthernetNetworkFactoryTest { @Test public void testNeedNetworkForOnUnprovisionedInterface() throws Exception { + initEthernetNetworkFactory(); createUnprovisionedInterface(TEST_IFACE); mNetFactory.needNetworkFor(createDefaultRequest()); verify(mIpClient).startProvisioning(any()); @@ -348,6 +362,7 @@ public class EthernetNetworkFactoryTest { @Test public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception { + initEthernetNetworkFactory(); createInterfaceUndergoingProvisioning(TEST_IFACE); mNetFactory.needNetworkFor(createDefaultRequest()); verify(mIpClient, never()).startProvisioning(any()); @@ -358,6 +373,7 @@ public class EthernetNetworkFactoryTest { @Test public void testProvisioningLoss() throws Exception { + initEthernetNetworkFactory(); when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams); createAndVerifyProvisionedInterface(TEST_IFACE); @@ -369,18 +385,24 @@ public class EthernetNetworkFactoryTest { @Test public void testProvisioningLossForDisappearedInterface() throws Exception { + initEthernetNetworkFactory(); // mocked method returns null by default, but just to be explicit in the test: when(mDeps.getNetworkInterfaceByName(eq(TEST_IFACE))).thenReturn(null); createAndVerifyProvisionedInterface(TEST_IFACE); triggerOnProvisioningFailure(); - verifyStop(); + // the interface disappeared and getNetworkInterfaceByName returns null, we should not retry verify(mIpClient, never()).startProvisioning(any()); + verify(mNetworkAgent, never()).register(); + verify(mIpClient, never()).shutdown(); + verify(mNetworkAgent, never()).unregister(); + verify(mIpClient, never()).startProvisioning(any()); } @Test public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception { + initEthernetNetworkFactory(); createUnprovisionedInterface(TEST_IFACE); mNetFactory.updateInterfaceLinkState(TEST_IFACE, false); @@ -399,6 +421,7 @@ public class EthernetNetworkFactoryTest { @Test public void testLinkPropertiesChanged() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); LinkProperties lp = new LinkProperties(); @@ -409,6 +432,7 @@ public class EthernetNetworkFactoryTest { @Test public void testNetworkUnwanted() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); mNetworkAgent.getCallbacks().onNetworkUnwanted(); @@ -418,6 +442,7 @@ public class EthernetNetworkFactoryTest { @Test public void testNetworkUnwantedWithStaleNetworkAgent() throws Exception { + initEthernetNetworkFactory(); // ensures provisioning is restarted after provisioning loss when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams); createAndVerifyProvisionedInterface(TEST_IFACE); @@ -441,6 +466,7 @@ public class EthernetNetworkFactoryTest { @Test public void testTransportOverrideIsCorrectlySet() throws Exception { + initEthernetNetworkFactory(); // createProvisionedInterface() has verifications in place for transport override // functionality which for EthernetNetworkFactory is network score and legacy type mappings. createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_ETHERNET, @@ -461,6 +487,7 @@ public class EthernetNetworkFactoryTest { @Test public void testReachabilityLoss() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); triggerOnReachabilityLost(); @@ -471,6 +498,7 @@ public class EthernetNetworkFactoryTest { @Test public void testIgnoreOnIpLayerStartedCallbackAfterIpClientHasStopped() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); mIpClientCallbacks.onProvisioningFailure(new LinkProperties()); mIpClientCallbacks.onProvisioningSuccess(new LinkProperties()); @@ -484,6 +512,7 @@ public class EthernetNetworkFactoryTest { @Test public void testIgnoreOnIpLayerStoppedCallbackAfterIpClientHasStopped() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams); mIpClientCallbacks.onProvisioningFailure(new LinkProperties()); @@ -497,26 +526,37 @@ public class EthernetNetworkFactoryTest { @Test public void testIgnoreLinkPropertiesCallbackAfterIpClientHasStopped() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); LinkProperties lp = new LinkProperties(); - mIpClientCallbacks.onProvisioningFailure(lp); + // The test requires the two proceeding methods to happen one after the other in ENF and + // verifies onLinkPropertiesChange doesn't complete execution for a downed interface. + // Posting is necessary as updateInterfaceLinkState with false will set mIpClientCallbacks + // to null which will throw an NPE in the test if executed synchronously. + mHandler.post(() -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false)); mIpClientCallbacks.onLinkPropertiesChange(lp); mLooper.dispatchAll(); - verifyStop(); + verifyStop(); // ipClient has been shut down first, we should not update verify(mNetworkAgent, never()).sendLinkPropertiesImpl(same(lp)); } @Test public void testIgnoreNeighborLossCallbackAfterIpClientHasStopped() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); - mIpClientCallbacks.onProvisioningFailure(new LinkProperties()); + + // The test requires the two proceeding methods to happen one after the other in ENF and + // verifies onReachabilityLost doesn't complete execution for a downed interface. + // Posting is necessary as updateInterfaceLinkState with false will set mIpClientCallbacks + // to null which will throw an NPE in the test if executed synchronously. + mHandler.post(() -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false)); mIpClientCallbacks.onReachabilityLost("Neighbor Lost"); mLooper.dispatchAll(); - verifyStop(); + verifyStop(); // ipClient has been shut down first, we should not update verify(mIpClient, never()).startProvisioning(any()); verify(mNetworkAgent, never()).register(); @@ -567,6 +607,7 @@ public class EthernetNetworkFactoryTest { @Test public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); final NetworkCapabilities capabilities = createDefaultFilterCaps(); final IpConfiguration ipConfiguration = createStaticIpConfig(); @@ -580,8 +621,71 @@ public class EthernetNetworkFactoryTest { assertNull(ret.second); } + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available + @Test + public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception { + initEthernetNetworkFactory(); + verifyNetworkManagementCallIsAbortedWhenInterrupted( + TEST_IFACE, + () -> mNetFactory.removeInterface(TEST_IFACE)); + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available + @Test + public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception { + initEthernetNetworkFactory(); + verifyNetworkManagementCallIsAbortedWhenInterrupted( + TEST_IFACE, + () -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false)); + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available + @Test + public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception { + initEthernetNetworkFactory(); + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener successfulListener = + new TestNetworkManagementListener(); + + // If two calls come in before the first one completes, the first listener will be aborted + // and the second one will be successful. + verifyNetworkManagementCallIsAbortedWhenInterrupted( + TEST_IFACE, + () -> { + mNetFactory.updateInterface( + TEST_IFACE, ipConfiguration, capabilities, successfulListener); + triggerOnProvisioningSuccess(); + }); + + final Pair successfulResult = + successfulListener.expectOnComplete(); + assertEquals(mMockNetwork, successfulResult.first); + assertNull(successfulResult.second); + } + + private void verifyNetworkManagementCallIsAbortedWhenInterrupted( + @NonNull final String iface, + @NonNull final Runnable interruptingRunnable) throws Exception { + createAndVerifyProvisionedInterface(iface); + final NetworkCapabilities capabilities = createDefaultFilterCaps(); + final IpConfiguration ipConfiguration = createStaticIpConfig(); + final TestNetworkManagementListener failedListener = new TestNetworkManagementListener(); + + // An active update request will be aborted on interrupt prior to provisioning completion. + mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener); + interruptingRunnable.run(); + + final Pair failedResult = + failedListener.expectOnComplete(); + assertNull(failedResult.first); + assertNotNull(failedResult.second); + assertTrue(failedResult.second.getMessage().contains("aborted")); + } + @Test public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception { + initEthernetNetworkFactory(); createAndVerifyProvisionedInterface(TEST_IFACE); final NetworkCapabilities capabilities = createDefaultFilterCaps(); final IpConfiguration ipConfiguration = createStaticIpConfig();