Merge "Cleaning EthernetNetworkFactoryTest Unprovisioned"
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index 875fc10..ef3abba 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -19,7 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.ConnectivityResources;
 import android.net.EthernetManager;
 import android.net.EthernetNetworkSpecifier;
 import android.net.IEthernetNetworkManagementListener;
@@ -49,6 +51,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.connectivity.resources.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.InterfaceParams;
@@ -69,6 +72,8 @@
 
     private final static int NETWORK_SCORE = 70;
     private static final String NETWORK_TYPE = "Ethernet";
+    private static final String LEGACY_TCP_BUFFER_SIZES =
+            "524288,1048576,3145728,524288,1048576,2097152";
 
     private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
             new ConcurrentHashMap<>();
@@ -94,6 +99,27 @@
         public InterfaceParams getNetworkInterfaceByName(String name) {
             return InterfaceParams.getByName(name);
         }
+
+        // TODO: remove legacy resource fallback after migrating its overlays.
+        private String getPlatformTcpBufferSizes(Context context) {
+            final Resources r = context.getResources();
+            final int resId = r.getIdentifier("config_ethernet_tcp_buffers", "string",
+                    context.getPackageName());
+            return r.getString(resId);
+        }
+
+        public String getTcpBufferSizesFromResource(Context context) {
+            final String tcpBufferSizes;
+            final String platformTcpBufferSizes = getPlatformTcpBufferSizes(context);
+            if (!LEGACY_TCP_BUFFER_SIZES.equals(platformTcpBufferSizes)) {
+                // Platform resource is not the historical default: use the overlay.
+                tcpBufferSizes = platformTcpBufferSizes;
+            } else {
+                final ConnectivityResources resources = new ConnectivityResources(context);
+                tcpBufferSizes = resources.get().getString(R.string.config_ethernet_tcp_buffers);
+            }
+            return tcpBufferSizes;
+        }
     }
 
     public static class ConfigurationException extends AndroidRuntimeException {
@@ -207,7 +233,8 @@
      * Update a network's configuration and restart it if necessary.
      *
      * @param ifaceName the interface name of the network to be updated.
-     * @param ipConfig the desired {@link IpConfiguration} for the given network.
+     * @param ipConfig the desired {@link IpConfiguration} for the given network or null. If
+     *                 {@code null} is passed, the existing IpConfiguration is not updated.
      * @param capabilities the desired {@link NetworkCapabilities} for the given network. If
      *                     {@code null} is passed, then the network's current
      *                     {@link NetworkCapabilities} will be used in support of existing APIs as
@@ -217,7 +244,7 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void updateInterface(@NonNull final String ifaceName,
-            @NonNull final IpConfiguration ipConfig,
+            @Nullable final IpConfiguration ipConfig,
             @Nullable final NetworkCapabilities capabilities,
             @Nullable final IEthernetNetworkManagementListener listener) {
         if (!hasInterface(ifaceName)) {
@@ -473,7 +500,7 @@
             mLegacyType = getLegacyType(mCapabilities);
         }
 
-        void updateInterface(@NonNull final IpConfiguration ipConfig,
+        void updateInterface(@Nullable final IpConfiguration ipConfig,
                 @Nullable final NetworkCapabilities capabilities,
                 @Nullable final IEthernetNetworkManagementListener listener) {
             if (DBG) {
@@ -484,7 +511,9 @@
                 );
             }
 
-            mIpConfig = ipConfig;
+            if (null != ipConfig){
+                mIpConfig = ipConfig;
+            }
             if (null != capabilities) {
                 setCapabilities(capabilities);
             }
@@ -518,8 +547,7 @@
             mIpClientCallback.awaitIpClientStart();
 
             if (sTcpBufferSizes == null) {
-                sTcpBufferSizes = mContext.getResources().getString(
-                        com.android.internal.R.string.config_ethernet_tcp_buffers);
+                sTcpBufferSizes = mDeps.getTcpBufferSizesFromResource(mContext);
             }
             provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
         }
diff --git a/service-t/src/com/android/server/ethernet/EthernetService.java b/service-t/src/com/android/server/ethernet/EthernetService.java
index e6fee9e..d405fd5 100644
--- a/service-t/src/com/android/server/ethernet/EthernetService.java
+++ b/service-t/src/com/android/server/ethernet/EthernetService.java
@@ -21,45 +21,27 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.util.Log;
-import com.android.server.SystemService;
 
 import java.util.Objects;
 
-public final class EthernetService extends SystemService {
-
+// TODO: consider renaming EthernetServiceImpl to EthernetService and deleting this file.
+public final class EthernetService {
     private static final String TAG = "EthernetService";
     private static final String THREAD_NAME = "EthernetServiceThread";
-    private final EthernetServiceImpl mImpl;
 
-    public EthernetService(Context context) {
-        super(context);
-        final HandlerThread handlerThread = new HandlerThread(THREAD_NAME);
-        handlerThread.start();
-        final Handler handler = handlerThread.getThreadHandler();
-        final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
-        mImpl = new EthernetServiceImpl(
-                context, handler,
-                new EthernetTracker(context, handler, factory, getNetd(context)));
-    }
-
-    private INetd getNetd(Context context) {
+    private static INetd getNetd(Context context) {
         final INetd netd =
                 INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
         Objects.requireNonNull(netd, "could not get netd instance");
         return netd;
     }
 
-    @Override
-    public void onStart() {
-        Log.i(TAG, "Registering service " + Context.ETHERNET_SERVICE);
-        publishBinderService(Context.ETHERNET_SERVICE, mImpl);
-    }
-
-    @Override
-    public void onBootPhase(int phase) {
-        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            mImpl.start();
-        }
+    public static EthernetServiceImpl create(Context context) {
+        final HandlerThread handlerThread = new HandlerThread(THREAD_NAME);
+        handlerThread.start();
+        final Handler handler = new Handler(handlerThread.getLooper());
+        final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
+        return new EthernetServiceImpl(context, handler,
+                new EthernetTracker(context, handler, factory, getNetd(context)));
     }
 }
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index ea241e1..074c81b 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -23,6 +23,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityResources;
 import android.net.EthernetManager;
 import android.net.IEthernetServiceListener;
 import android.net.IEthernetNetworkManagementListener;
@@ -80,6 +82,7 @@
     private static final boolean DBG = EthernetNetworkFactory.DBG;
 
     private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+";
+    private static final String LEGACY_IFACE_REGEXP = "eth\\d";
 
     /**
      * Interface names we track. This is a product-dependent regular expression, plus,
@@ -102,6 +105,7 @@
     private final Handler mHandler;
     private final EthernetNetworkFactory mFactory;
     private final EthernetConfigStore mConfigStore;
+    private final Dependencies mDeps;
 
     private final RemoteCallbackList<IEthernetServiceListener> mListeners =
             new RemoteCallbackList<>();
@@ -123,19 +127,72 @@
         }
     }
 
+    public static class Dependencies {
+        // TODO: remove legacy resource fallback after migrating its overlays.
+        private String getPlatformRegexResource(Context context) {
+            final Resources r = context.getResources();
+            final int resId =
+                r.getIdentifier("config_ethernet_iface_regex", "string", context.getPackageName());
+            return r.getString(resId);
+        }
+
+        // TODO: remove legacy resource fallback after migrating its overlays.
+        private String[] getPlatformInterfaceConfigs(Context context) {
+            final Resources r = context.getResources();
+            final int resId = r.getIdentifier("config_ethernet_interfaces", "array",
+                    context.getPackageName());
+            return r.getStringArray(resId);
+        }
+
+        public String getInterfaceRegexFromResource(Context context) {
+            final String platformRegex = getPlatformRegexResource(context);
+            final String match;
+            if (!LEGACY_IFACE_REGEXP.equals(platformRegex)) {
+                // Platform resource is not the historical default: use the overlay
+                match = platformRegex;
+            } else {
+                final ConnectivityResources resources = new ConnectivityResources(context);
+                match = resources.get().getString(
+                        com.android.connectivity.resources.R.string.config_ethernet_iface_regex);
+            }
+            return match;
+        }
+
+        public String[] getInterfaceConfigFromResource(Context context) {
+            final String[] platformInterfaceConfigs = getPlatformInterfaceConfigs(context);
+            final String[] interfaceConfigs;
+            if (platformInterfaceConfigs.length != 0) {
+                // Platform resource is not the historical default: use the overlay
+                interfaceConfigs = platformInterfaceConfigs;
+            } else {
+                final ConnectivityResources resources = new ConnectivityResources(context);
+                interfaceConfigs = resources.get().getStringArray(
+                        com.android.connectivity.resources.R.array.config_ethernet_interfaces);
+            }
+            return interfaceConfigs;
+        }
+    }
+
     EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
             @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) {
+        this(context, handler, factory, netd, new Dependencies());
+    }
+
+    @VisibleForTesting
+    EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
+            @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd,
+            @NonNull final Dependencies deps) {
         mContext = context;
         mHandler = handler;
         mFactory = factory;
         mNetd = netd;
+        mDeps = deps;
 
         // Interface match regex.
         updateIfaceMatchRegexp();
 
         // Read default Ethernet interface configuration from resources
-        final String[] interfaceConfigs = context.getResources().getStringArray(
-                com.android.internal.R.array.config_ethernet_interfaces);
+        final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context);
         for (String strConfig : interfaceConfigs) {
             parseEthernetConfig(strConfig);
         }
@@ -232,15 +289,20 @@
 
     @VisibleForTesting(visibility = PACKAGE)
     protected void updateConfiguration(@NonNull final String iface,
-            @NonNull final IpConfiguration ipConfig,
+            @Nullable final IpConfiguration ipConfig,
             @Nullable final NetworkCapabilities capabilities,
             @Nullable final IEthernetNetworkManagementListener listener) {
         if (DBG) {
             Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
                     + ", ipConfig: " + ipConfig);
         }
-        final IpConfiguration localIpConfig = new IpConfiguration(ipConfig);
-        writeIpConfiguration(iface, localIpConfig);
+
+        final IpConfiguration localIpConfig = ipConfig == null
+                ? null : new IpConfiguration(ipConfig);
+        if (ipConfig != null) {
+            writeIpConfiguration(iface, localIpConfig);
+        }
+
         if (null != capabilities) {
             mNetworkCapabilities.put(iface, capabilities);
         }
@@ -735,8 +797,7 @@
     }
 
     private void updateIfaceMatchRegexp() {
-        final String match = mContext.getResources().getString(
-                com.android.internal.R.string.config_ethernet_iface_regex);
+        final String match = mDeps.getInterfaceRegexFromResource(mContext);
         mIfaceMatch = mIncludeTestInterfaces
                 ? "(" + match + "|" + TEST_IFACE_REGEXP + ")"
                 : match;
diff --git a/tests/ethernet/Android.bp b/tests/ethernet/Android.bp
index 28b13c5..6cfebdc 100644
--- a/tests/ethernet/Android.bp
+++ b/tests/ethernet/Android.bp
@@ -17,10 +17,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// TODO: merge the tests into service-connectivity tests after
+// ethernet service migration completes. So far just import the
+// ethernet service source to fix the dependencies.
 android_test {
     name: "EthernetServiceTests",
 
-    srcs: ["java/**/*.java"],
+    srcs: [
+        ":ethernet-service-updatable-sources",
+        ":services.connectivity-ethernet-sources",
+        "java/**/*.java",
+    ],
 
     certificate: "platform",
     platform_apis: true,
@@ -29,11 +36,13 @@
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
+        "framework-connectivity.impl",
+        "framework-connectivity-t.impl",
+        "ServiceConnectivityResources",
     ],
 
     static_libs: [
         "androidx.test.rules",
-        "ethernet-service",
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
         "net-tests-utils",
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 28fcf3e..e256add 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -63,7 +63,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
+import com.android.connectivity.resources.R;
 import com.android.net.module.util.InterfaceParams;
 
 import com.android.testutils.DevSdkIgnoreRule;
@@ -174,8 +174,7 @@
     }
 
     private void setupContext() {
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getString(R.string.config_ethernet_tcp_buffers)).thenReturn("");
+        when(mDeps.getTcpBufferSizesFromResource(eq(mContext))).thenReturn("");
     }
 
     @After
@@ -771,4 +770,28 @@
         verifyNoStopOrStart();
         assertFailedListener(listener, "can't be updated as it is not available");
     }
+
+    @Test
+    public void testUpdateInterfaceWithNullIpConfiguration() throws Exception {
+        initEthernetNetworkFactory();
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+
+        final IpConfiguration initialIpConfig = createStaticIpConfig();
+        mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/,
+                null /*listener*/);
+        triggerOnProvisioningSuccess();
+        verifyRestart(initialIpConfig);
+
+        // TODO: have verifyXyz functions clear invocations.
+        clearInvocations(mDeps);
+        clearInvocations(mIpClient);
+        clearInvocations(mNetworkAgent);
+
+
+        // verify that sending a null ipConfig does not update the current ipConfig.
+        mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/,
+                null /*listener*/);
+        triggerOnProvisioningSuccess();
+        verifyRestart(initialIpConfig);
+    }
 }
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
index 175a97d..2131f7f 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -60,6 +60,10 @@
             new EthernetNetworkUpdateRequest.Builder()
                     .setIpConfiguration(new IpConfiguration())
                     .build();
+    private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_IP_CONFIG =
+            new EthernetNetworkUpdateRequest.Builder()
+                    .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+                    .build();
     private static final IEthernetNetworkManagementListener NULL_LISTENER = null;
     private EthernetServiceImpl mEthernetServiceImpl;
     @Mock private Context mContext;
@@ -276,6 +280,15 @@
     }
 
     @Test
+    public void testUpdateConfigurationAcceptsRequestWithNullIpConfiguration() {
+        mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_IP_CONFIG,
+                NULL_LISTENER);
+        verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
+                eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getIpConfiguration()),
+                eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()), isNull());
+    }
+
+    @Test
     public void testUpdateConfigurationRejectsInvalidTestRequest() {
         enableTestInterface();
         assertThrows(IllegalArgumentException.class, () -> {
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
index d86cc0f..ef70d94 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -48,7 +47,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
+import com.android.connectivity.resources.R;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
@@ -73,7 +72,7 @@
     @Mock private Context mContext;
     @Mock private EthernetNetworkFactory mFactory;
     @Mock private INetd mNetd;
-    @Mock Resources mResources;
+    @Mock private EthernetTracker.Dependencies mDeps;
 
     @Before
     public void setUp() throws RemoteException {
@@ -83,7 +82,8 @@
         when(mNetd.interfaceGetList()).thenReturn(new String[0]);
         mHandlerThread = new HandlerThread(THREAD_NAME);
         mHandlerThread.start();
-        tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd);
+        tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd,
+                mDeps);
     }
 
     @After
@@ -92,9 +92,8 @@
     }
 
     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();
+        when(mDeps.getInterfaceRegexFromResource(eq(mContext))).thenReturn("");
+        when(mDeps.getInterfaceConfigFromResource(eq(mContext))).thenReturn(new String[0]);
     }
 
     private void waitForIdle() {