Implementation of Eth Service updateConfiguration

EthernetServiceImpl#updateConfiguration API implementation to allow
for dynamically updating the network capability or ip configuration of
an ethernet based interface.

Bug: 210485380
Test: atest EthernetServiceTests
Change-Id: Idd3b7875a24643d245d0f4bb6f2f4c459898116e
diff --git a/tests/ethernet/Android.bp b/tests/ethernet/Android.bp
index 1bc9352..93a8f6c 100644
--- a/tests/ethernet/Android.bp
+++ b/tests/ethernet/Android.bp
@@ -36,6 +36,8 @@
         "ethernet-service",
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
+        "net-tests-utils",
+        "services.core",
         "services.net",
     ],
 }
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 990ee5f..44ed26c 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -16,9 +16,11 @@
 
 package com.android.server.ethernet;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -36,17 +38,24 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.EthernetNetworkSpecifier;
+import android.net.IInternalNetworkManagementListener;
+import android.net.InternalNetworkManagementException;
 import android.net.IpConfiguration;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
+import android.net.StaticIpConfiguration;
 import android.net.ip.IpClientCallbacks;
 import android.net.ip.IpClientManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -61,10 +70,20 @@
 import org.mockito.Mock;
 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
 public class EthernetNetworkFactoryTest {
+    private static final int TIMEOUT_MS = 2_000;
     private static final String TEST_IFACE = "test123";
+    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 Handler mHandler;
     private EthernetNetworkFactory mNetFactory = null;
@@ -75,13 +94,13 @@
     @Mock private IpClientManager mIpClient;
     @Mock private EthernetNetworkAgent mNetworkAgent;
     @Mock private InterfaceParams mInterfaceParams;
+    @Mock private Network mMockNetwork;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mHandler = new Handler(mLooper.getLooper());
-        mNetFactory = new EthernetNetworkFactory(mHandler, mContext, createDefaultFilterCaps(),
-                mDeps);
+        mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps);
 
         setupNetworkAgentMock();
         setupIpClientMock();
@@ -100,6 +119,8 @@
                                                NetworkProvider provider,
                                                EthernetNetworkAgent.Callbacks cb) {
                                            when(mNetworkAgent.getCallbacks()).thenReturn(cb);
+                                           when(mNetworkAgent.getNetwork())
+                                                   .thenReturn(mMockNetwork);
                                            return mNetworkAgent;
                                        }
                                    }
@@ -185,6 +206,19 @@
         return ipConfig;
     }
 
+    /**
+     * Create an {@link IpConfiguration} with an associated {@link StaticIpConfiguration}.
+     *
+     * @return {@link IpConfiguration} with its {@link StaticIpConfiguration} set.
+     */
+    private IpConfiguration createStaticIpConfig() {
+        final IpConfiguration ipConfig = new IpConfiguration();
+        ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
+        ipConfig.setStaticIpConfiguration(
+                new StaticIpConfiguration.Builder().setIpAddress(LINK_ADDR).build());
+        return ipConfig;
+    }
+
     // creates an interface with provisioning in progress (since updating the interface link state
     // automatically starts the provisioning process)
     private void createInterfaceUndergoingProvisioning(String iface) {
@@ -194,10 +228,11 @@
 
     private void createInterfaceUndergoingProvisioning(
             @NonNull final String iface, final int transportType) {
-        mNetFactory.addInterface(iface, iface, createInterfaceCapsBuilder(transportType).build(),
-                createDefaultIpConfig());
+        final IpConfiguration ipConfig = createDefaultIpConfig();
+        mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
+                createInterfaceCapsBuilder(transportType).build());
         assertTrue(mNetFactory.updateInterfaceLinkState(iface, true));
-        verifyStart();
+        verifyStart(ipConfig);
         clearInvocations(mDeps);
         clearInvocations(mIpClient);
     }
@@ -431,13 +466,19 @@
         triggerOnReachabilityLost();
 
         // Reachability loss should trigger a stop and start, since the interface is still there
-        verifyStop();
-        verifyStart();
+        verifyRestart(createDefaultIpConfig());
     }
 
-    private void verifyStart() {
+    private void verifyRestart(@NonNull final IpConfiguration ipConfig) {
+        verifyStop();
+        verifyStart(ipConfig);
+    }
+
+    private void verifyStart(@NonNull final IpConfiguration ipConfig) {
         verify(mDeps).makeIpClient(any(Context.class), anyString(), any());
-        verify(mIpClient).startProvisioning(any());
+        verify(mIpClient).startProvisioning(
+                argThat(x -> Objects.equals(x.mStaticIpConfig, ipConfig.getStaticIpConfiguration()))
+        );
     }
 
     private void verifyStop() {
@@ -449,4 +490,56 @@
         verify(mNetworkAgent).register();
         verify(mNetworkAgent).markConnected();
     }
+
+    private static final class TestNetworkManagementListener
+            implements IInternalNetworkManagementListener {
+        private final CompletableFuture<Pair<Network, InternalNetworkManagementException>> mDone
+                = new CompletableFuture<>();
+
+        @Override
+        public void onComplete(final Network network,
+                final InternalNetworkManagementException exception) {
+            mDone.complete(new Pair<>(network, exception));
+        }
+
+        Pair<Network, InternalNetworkManagementException> expectOnComplete() throws Exception {
+            return mDone.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception {
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        final NetworkCapabilities capabilities = createDefaultFilterCaps();
+        final IpConfiguration ipConfiguration = createStaticIpConfig();
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+        mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+        triggerOnProvisioningSuccess();
+
+        final Pair<Network, InternalNetworkManagementException> ret = listener.expectOnComplete();
+        assertEquals(mMockNetwork, ret.first);
+        assertNull(ret.second);
+    }
+
+    @Test
+    public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception {
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        final NetworkCapabilities capabilities = createDefaultFilterCaps();
+        final IpConfiguration ipConfiguration = createStaticIpConfig();
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+        mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+        triggerOnProvisioningSuccess();
+
+        listener.expectOnComplete();
+        verify(mDeps).makeEthernetNetworkAgent(any(), any(),
+                eq(capabilities), any(), any(), any(), any());
+        verifyRestart(ipConfiguration);
+    }
 }
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
index 516e574..1c08883 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -18,13 +18,17 @@
 
 import static org.junit.Assert.assertThrows;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.IInternalNetworkManagementListener;
 import android.net.InternalNetworkUpdateRequest;
 import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
 import android.net.StaticIpConfiguration;
 import android.os.Handler;
 
@@ -41,6 +45,10 @@
 @SmallTest
 public class EthernetServiceImplTest {
     private static final String TEST_IFACE = "test123";
+    private static final InternalNetworkUpdateRequest UPDATE_REQUEST =
+            new InternalNetworkUpdateRequest(
+                    new StaticIpConfiguration(), new NetworkCapabilities.Builder().build());
+    private static final IInternalNetworkManagementListener NULL_LISTENER = null;
     private EthernetServiceImpl mEthernetServiceImpl;
     @Mock private Context mContext;
     @Mock private Handler mHandler;
@@ -69,10 +77,8 @@
     public void testUpdateConfigurationRejectsWhenEthNotStarted() {
         mEthernetServiceImpl.mStarted.set(false);
         assertThrows(IllegalStateException.class, () -> {
-            final InternalNetworkUpdateRequest r =
-                    new InternalNetworkUpdateRequest(new StaticIpConfiguration(), null);
-
-            mEthernetServiceImpl.updateConfiguration("" /* iface */, r, null /* listener */);
+            mEthernetServiceImpl.updateConfiguration(
+                    "" /* iface */, UPDATE_REQUEST, null /* listener */);
         });
     }
 
@@ -95,24 +101,21 @@
     @Test
     public void testUpdateConfigurationRejectsNullIface() {
         assertThrows(NullPointerException.class, () -> {
-            final InternalNetworkUpdateRequest r =
-                    new InternalNetworkUpdateRequest(new StaticIpConfiguration(), null);
-
-            mEthernetServiceImpl.updateConfiguration(null /* iface */, r, null /* listener */);
+            mEthernetServiceImpl.updateConfiguration(null, UPDATE_REQUEST, NULL_LISTENER);
         });
     }
 
     @Test
     public void testConnectNetworkRejectsNullIface() {
         assertThrows(NullPointerException.class, () -> {
-            mEthernetServiceImpl.connectNetwork(null /* iface */, null /* listener */);
+            mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER);
         });
     }
 
     @Test
     public void testDisconnectNetworkRejectsNullIface() {
         assertThrows(NullPointerException.class, () -> {
-            mEthernetServiceImpl.disconnectNetwork(null /* iface */, null /* listener */);
+            mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER);
         });
     }
 
@@ -120,10 +123,7 @@
     public void testUpdateConfigurationRejectsWithoutAutomotiveFeature() {
         toggleAutomotiveFeature(false);
         assertThrows(UnsupportedOperationException.class, () -> {
-            final InternalNetworkUpdateRequest r =
-                    new InternalNetworkUpdateRequest(new StaticIpConfiguration(), null);
-
-            mEthernetServiceImpl.updateConfiguration("" /* iface */, r, null /* listener */);
+            mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
         });
     }
 
@@ -131,7 +131,7 @@
     public void testConnectNetworkRejectsWithoutAutomotiveFeature() {
         toggleAutomotiveFeature(false);
         assertThrows(UnsupportedOperationException.class, () -> {
-            mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */);
+            mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER);
         });
     }
 
@@ -139,7 +139,7 @@
     public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() {
         toggleAutomotiveFeature(false);
         assertThrows(UnsupportedOperationException.class, () -> {
-            mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */);
+            mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER);
         });
     }
 
@@ -152,10 +152,7 @@
     public void testUpdateConfigurationRejectsWithUntrackedIface() {
         shouldTrackIface(TEST_IFACE, false);
         assertThrows(UnsupportedOperationException.class, () -> {
-            final InternalNetworkUpdateRequest r =
-                    new InternalNetworkUpdateRequest(new StaticIpConfiguration(), null);
-
-            mEthernetServiceImpl.updateConfiguration(TEST_IFACE, r, null /* listener */);
+            mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
         });
     }
 
@@ -163,7 +160,7 @@
     public void testConnectNetworkRejectsWithUntrackedIface() {
         shouldTrackIface(TEST_IFACE, false);
         assertThrows(UnsupportedOperationException.class, () -> {
-            mEthernetServiceImpl.connectNetwork(TEST_IFACE, null /* listener */);
+            mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
         });
     }
 
@@ -171,11 +168,20 @@
     public void testDisconnectNetworkRejectsWithUntrackedIface() {
         shouldTrackIface(TEST_IFACE, false);
         assertThrows(UnsupportedOperationException.class, () -> {
-            mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, null /* listener */);
+            mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
         });
     }
 
     private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) {
         doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface);
     }
+
+    @Test
+    public void testUpdateConfiguration() {
+        mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+        verify(mEthernetTracker).updateConfiguration(
+                eq(TEST_IFACE),
+                eq(UPDATE_REQUEST.getIpConfig()),
+                eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER));
+    }
 }
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
index 6ea2154..5aca2e4 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -19,20 +19,35 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.net.InetAddresses;
+import android.net.IInternalNetworkManagementListener;
+import android.net.INetd;
 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.os.HandlerThread;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
+import com.android.testutils.HandlerUtils;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -40,6 +55,39 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class EthernetTrackerTest {
+    private static final int TIMEOUT_MS = 1_000;
+    private static final String THREAD_NAME = "EthernetServiceThread";
+    private EthernetTracker tracker;
+    private HandlerThread mHandlerThread;
+    @Mock private Context mContext;
+    @Mock private EthernetNetworkFactory mFactory;
+    @Mock private INetd mNetd;
+    @Mock Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        initMockResources();
+        mHandlerThread = new HandlerThread(THREAD_NAME);
+        mHandlerThread.start();
+        tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd);
+    }
+
+    @After
+    public void cleanUp() {
+        mHandlerThread.quitSafely();
+    }
+
+    private void initMockResources() {
+        doReturn("").when(mResources).getString(R.string.config_ethernet_iface_regex);
+        doReturn(new String[0]).when(mResources).getStringArray(R.array.config_ethernet_interfaces);
+        doReturn(mResources).when(mContext).getResources();
+    }
+
+    private void waitForIdle() {
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+    }
+
     /**
      * Test: Creation of various valid static IP configurations
      */
@@ -246,16 +294,16 @@
 
     @Test
     public void testCreateEthernetTrackerConfigReturnsCorrectValue() {
-        final String name = "1";
+        final String iface = "1";
         final String capabilities = "2";
         final String ipConfig = "3";
         final String transport = "4";
-        final String configString = String.join(";", name, capabilities, ipConfig, transport);
+        final String configString = String.join(";", iface, capabilities, ipConfig, transport);
 
         final EthernetTracker.EthernetTrackerConfig config =
                 EthernetTracker.createEthernetTrackerConfig(configString);
 
-        assertEquals(name, config.mName);
+        assertEquals(iface, config.mIface);
         assertEquals(capabilities, config.mCapabilities);
         assertEquals(ipConfig, config.mIpConfig);
         assertEquals(transport, config.mTransport);
@@ -266,4 +314,19 @@
         assertThrows(NullPointerException.class,
                 () -> EthernetTracker.createEthernetTrackerConfig(null));
     }
+
+    @Test
+    public void testUpdateConfiguration() {
+        final String iface = "testiface";
+        final NetworkCapabilities capabilities = new NetworkCapabilities.Builder().build();
+        final StaticIpConfiguration staticIpConfig = new StaticIpConfiguration();
+        final IInternalNetworkManagementListener listener = null;
+
+        tracker.updateConfiguration(iface, staticIpConfig, capabilities, listener);
+        waitForIdle();
+
+        verify(mFactory).updateInterface(
+                eq(iface), eq(EthernetTracker.createIpConfiguration(staticIpConfig)),
+                eq(capabilities), eq(listener));
+    }
 }