Empty merge of sc-v2-dev-plus-aosp-without-vendor@8084891

Bug: 214455710
Merged-In: Idd3b7875a24643d245d0f4bb6f2f4c459898116e
Change-Id: I74463ff94a96d5c0807ef33b41b74dc580b039cb
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index b556f6c..a15fec4 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -21,11 +21,13 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.EthernetNetworkSpecifier;
-import android.net.IInternalNetworkManagementListener;
+import android.net.IEthernetNetworkManagementListener;
+import android.net.EthernetNetworkManagementException;
 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;
@@ -193,24 +195,19 @@
      *                     {@code null} is passed, then the network's current
      *                     {@link NetworkCapabilities} will be used in support of existing APIs as
      *                     the public API does not allow this.
-     * @param listener an optional {@link IInternalNetworkManagementListener} to notify callers of
+     * @param listener an optional {@link IEthernetNetworkManagementListener} to notify callers of
      *                 completion.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void updateInterface(@NonNull final String ifaceName,
             @NonNull final IpConfiguration ipConfig,
             @Nullable final NetworkCapabilities capabilities,
-            @Nullable final IInternalNetworkManagementListener listener) {
+            @Nullable final IEthernetNetworkManagementListener 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 @@
     void removeInterface(String interfaceName) {
         NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
         if (iface != null) {
+            iface.maybeSendNetworkManagementCallbackForAbort();
             iface.stop();
         }
 
@@ -254,8 +252,13 @@
     }
 
     /** Returns true if state has been modified */
-    boolean updateInterfaceLinkState(String ifaceName, boolean up) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected boolean updateInterfaceLinkState(@NonNull final String ifaceName, final boolean up,
+            @Nullable final IEthernetNetworkManagementListener listener) {
         if (!mTrackingInterfaces.containsKey(ifaceName)) {
+            maybeSendNetworkManagementCallback(listener, null,
+                    new EthernetNetworkManagementException(
+                            ifaceName + " can't be updated as it is not available."));
             return false;
         }
 
@@ -264,7 +267,7 @@
         }
 
         NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
-        return iface.updateLinkState(up);
+        return iface.updateLinkState(up, listener);
     }
 
     boolean hasInterface(String ifaceName) {
@@ -302,6 +305,21 @@
         return network;
     }
 
+    private static void maybeSendNetworkManagementCallback(
+            @Nullable final IEthernetNetworkManagementListener listener,
+            @Nullable final Network network,
+            @Nullable final EthernetNetworkManagementException 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 +338,7 @@
 
         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 +365,14 @@
 
         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 IEthernetNetworkManagementListener mNetworkManagementListener;
+
+            EthernetIpClientCallback(@Nullable final IEthernetNetworkManagementListener listener) {
+                mNetworkManagementListener = listener;
+            }
 
             @Override
             public void onIpClientCreated(IIpClient ipClient) {
@@ -368,12 +390,12 @@
 
             @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 +453,25 @@
             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 IEthernetNetworkManagementListener 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 +479,10 @@
         }
 
         private void start() {
+            start(null);
+        }
+
+        private void start(@Nullable final IEthernetNetworkManagementListener listener) {
             if (mIpClient != null) {
                 if (DBG) Log.d(TAG, "IpClient already started");
                 return;
@@ -470,9 +491,10 @@
                 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,7 +502,16 @@
             provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
         }
 
-        void onIpLayerStarted(LinkProperties linkProperties) {
+        void onIpLayerStarted(@NonNull final LinkProperties linkProperties,
+                @Nullable final IEthernetNetworkManagementListener 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;
+            }
             if (mNetworkAgent != null) {
                 Log.e(TAG, "Already have a NetworkAgent - aborting new request");
                 stop();
@@ -512,33 +543,61 @@
                     });
             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(@Nullable final IEthernetNetworkManagementListener 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;
             }
+            // 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);
         }
 
-        void onIpLayerStopped(LinkProperties linkProperties) {
-            // 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();
+        private void maybeSendNetworkManagementCallbackForAbort() {
+            realizeNetworkManagementCallback(null,
+                    new EthernetNetworkManagementException(
+                            "The IP provisioning request has been aborted."));
+        }
+
+        // Must be called on the handler thread
+        private void realizeNetworkManagementCallback(@Nullable final Network network,
+                @Nullable final EthernetNetworkManagementException 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());
             }
         }
 
         void updateLinkProperties(LinkProperties linkProperties) {
+            if(mIpClient == null) {
+                if (DBG) Log.d(TAG, "IpClient is not initialized.");
+                return;
+            }
             mLinkProperties = linkProperties;
             if (mNetworkAgent != null) {
                 mNetworkAgent.sendLinkPropertiesImpl(linkProperties);
@@ -546,6 +605,10 @@
         }
 
         void updateNeighborLostEvent(String logMsg) {
+            if(mIpClient == null) {
+                if (DBG) Log.d(TAG, "IpClient is not initialized.");
+                return;
+            }
             Log.i(TAG, "updateNeighborLostEvent " + logMsg);
             // Reachability lost will be seen only if the gateway is not reachable.
             // Since ethernet FW doesn't have the mechanism to scan for new networks
@@ -557,13 +620,27 @@
         }
 
         /** Returns true if state has been modified */
-        boolean updateLinkState(boolean up) {
-            if (mLinkUp == up) return false;
+        boolean updateLinkState(final boolean up,
+                @Nullable final IEthernetNetworkManagementListener listener) {
+            if (mLinkUp == up)  {
+                EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, null,
+                        new EthernetNetworkManagementException(
+                                "No changes with requested link state " + up + " for " + name));
+                return false;
+            }
             mLinkUp = up;
 
-            stop();
-            if (up) {
-                start();
+            if (!up) { // was up, goes down
+                // Save an instance of the current network to use with the callback before stop().
+                final Network network = mNetworkAgent != null ? mNetworkAgent.getNetwork() : null;
+                // Send an abort on a provisioning request callback if necessary before stopping.
+                maybeSendNetworkManagementCallbackForAbort();
+                stop();
+                // If only setting the interface down, send a callback to signal completion.
+                EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, network, null);
+            } else { // was down, goes up
+                stop();
+                start(listener);
             }
 
             return true;
@@ -611,10 +688,14 @@
                         .build();
         }
 
-        void restart(){
+        void restart() {
+            restart(null);
+        }
+
+        void restart(@Nullable final IEthernetNetworkManagementListener listener){
             if (DBG) Log.d(TAG, "reconnecting Ethernet");
             stop();
-            start();
+            start(listener);
         }
 
         @Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetService.java b/service-t/src/com/android/server/ethernet/EthernetService.java
index 492a55a..e6fee9e 100644
--- a/service-t/src/com/android/server/ethernet/EthernetService.java
+++ b/service-t/src/com/android/server/ethernet/EthernetService.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.net.INetd;
-import android.net.util.NetdService;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -41,11 +40,12 @@
         final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
         mImpl = new EthernetServiceImpl(
                 context, handler,
-                new EthernetTracker(context, handler, factory, getNetd()));
+                new EthernetTracker(context, handler, factory, getNetd(context)));
     }
 
-    private INetd getNetd() {
-        final INetd netd = NetdService.getInstance();
+    private 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;
     }
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index b284477..ffd6d40 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -22,9 +22,9 @@
 import android.content.pm.PackageManager;
 import android.net.IEthernetManager;
 import android.net.IEthernetServiceListener;
-import android.net.IInternalNetworkManagementListener;
+import android.net.IEthernetNetworkManagementListener;
 import android.net.ITetheredInterfaceCallback;
-import android.net.InternalNetworkUpdateRequest;
+import android.net.EthernetNetworkUpdateRequest;
 import android.net.IpConfiguration;
 import android.os.Binder;
 import android.os.Handler;
@@ -208,6 +208,12 @@
         pw.decreaseIndent();
     }
 
+    private void enforceNetworkManagementPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_ETHERNET_NETWORKS,
+                "EthernetServiceImpl");
+    }
+
     /**
      * Validate the state of ethernet for APIs tied to network management.
      *
@@ -216,19 +222,19 @@
      */
     private void validateNetworkManagementState(@NonNull final String iface,
             final @NonNull String methodName) {
+        enforceAutomotiveDevice(methodName);
+        enforceNetworkManagementPermission();
         logIfEthernetNotStarted();
 
-        // TODO: add permission check here for MANAGE_INTERNAL_NETWORKS when it's available.
         Objects.requireNonNull(iface, "Pass a non-null iface.");
         Objects.requireNonNull(methodName, "Pass a non-null methodName.");
-        enforceAutomotiveDevice(methodName);
         enforceInterfaceIsTracked(iface);
     }
 
     @Override
     public void updateConfiguration(@NonNull final String iface,
-            @NonNull final InternalNetworkUpdateRequest request,
-            @Nullable final IInternalNetworkManagementListener listener) {
+            @NonNull final EthernetNetworkUpdateRequest request,
+            @Nullable final IEthernetNetworkManagementListener listener) {
         Log.i(TAG, "updateConfiguration called with: iface=" + iface
                 + ", request=" + request + ", listener=" + listener);
         validateNetworkManagementState(iface, "updateConfiguration()");
@@ -240,15 +246,17 @@
 
     @Override
     public void connectNetwork(@NonNull final String iface,
-            @Nullable final IInternalNetworkManagementListener listener) {
+            @Nullable final IEthernetNetworkManagementListener listener) {
         Log.i(TAG, "connectNetwork called with: iface=" + iface + ", listener=" + listener);
         validateNetworkManagementState(iface, "connectNetwork()");
+        mTracker.connectNetwork(iface, listener);
     }
 
     @Override
     public void disconnectNetwork(@NonNull final String iface,
-            @Nullable final IInternalNetworkManagementListener listener) {
+            @Nullable final IEthernetNetworkManagementListener listener) {
         Log.i(TAG, "disconnectNetwork called with: iface=" + iface + ", listener=" + listener);
         validateNetworkManagementState(iface, "disconnectNetwork()");
+        mTracker.disconnectNetwork(iface, listener);
     }
 }
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 0b6547d..6c8e6d3 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -24,7 +24,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.IEthernetServiceListener;
-import android.net.IInternalNetworkManagementListener;
+import android.net.IEthernetNetworkManagementListener;
 import android.net.INetd;
 import android.net.ITetheredInterfaceCallback;
 import android.net.InterfaceConfigurationParcel;
@@ -34,6 +34,7 @@
 import android.net.LinkAddress;
 import android.net.NetworkCapabilities;
 import android.net.StaticIpConfiguration;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -177,7 +178,7 @@
     protected void updateConfiguration(@NonNull final String iface,
             @NonNull final StaticIpConfiguration staticIpConfig,
             @NonNull final NetworkCapabilities capabilities,
-            @Nullable final IInternalNetworkManagementListener listener) {
+            @Nullable final IEthernetNetworkManagementListener listener) {
         if (DBG) {
             Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
                     + ", staticIpConfig: " + staticIpConfig);
@@ -189,6 +190,18 @@
                 mFactory.updateInterface(iface, ipConfig, capabilities, listener));
     }
 
+    @VisibleForTesting(visibility = PACKAGE)
+    protected void connectNetwork(@NonNull final String iface,
+            @Nullable final IEthernetNetworkManagementListener listener) {
+        mHandler.post(() -> updateInterfaceState(iface, true, listener));
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    protected void disconnectNetwork(@NonNull final String iface,
+            @Nullable final IEthernetNetworkManagementListener listener) {
+        mHandler.post(() -> updateInterfaceState(iface, false, listener));
+    }
+
     IpConfiguration getIpConfiguration(String iface) {
         return mIpConfigurations.get(iface);
     }
@@ -353,9 +366,14 @@
     }
 
     private void updateInterfaceState(String iface, boolean up) {
+        updateInterfaceState(iface, up, null /* listener */);
+    }
+
+    private void updateInterfaceState(@NonNull final String iface, final boolean up,
+            @Nullable final IEthernetNetworkManagementListener listener) {
         final int mode = getInterfaceMode(iface);
         final boolean factoryLinkStateUpdated = (mode == INTERFACE_MODE_CLIENT)
-                && mFactory.updateInterfaceLinkState(iface, up);
+                && mFactory.updateInterfaceLinkState(iface, up, listener);
 
         if (factoryLinkStateUpdated) {
             boolean restricted = isRestrictedInterface(iface);
@@ -662,7 +680,13 @@
     }
 
     private void postAndWaitForRunnable(Runnable r) {
-        mHandler.runWithScissors(r, 2000L /* timeout */);
+        final ConditionVariable cv = new ConditionVariable();
+        if (mHandler.post(() -> {
+            r.run();
+            cv.open();
+        })) {
+            cv.block(2000L);
+        }
     }
 
     void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 44ed26c..e10497e 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -16,11 +16,13 @@
 
 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;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -38,8 +40,8 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.EthernetNetworkSpecifier;
-import android.net.IInternalNetworkManagementListener;
-import android.net.InternalNetworkManagementException;
+import android.net.IEthernetNetworkManagementListener;
+import android.net.EthernetNetworkManagementException;
 import android.net.IpConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -63,6 +65,8 @@
 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,19 +76,18 @@
 
 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 IEthernetNetworkManagementListener NULL_LISTENER = null;
     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 @@
     @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() {
@@ -231,7 +238,7 @@
         final IpConfiguration ipConfig = createDefaultIpConfig();
         mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
                 createInterfaceCapsBuilder(transportType).build());
-        assertTrue(mNetFactory.updateInterfaceLinkState(iface, true));
+        assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
         verifyStart(ipConfig);
         clearInvocations(mDeps);
         clearInvocations(mIpClient);
@@ -288,6 +295,7 @@
 
     @Test
     public void testAcceptRequest() throws Exception {
+        initEthernetNetworkFactory();
         createInterfaceUndergoingProvisioning(TEST_IFACE);
         assertTrue(mNetFactory.acceptRequest(createDefaultRequest()));
 
@@ -299,38 +307,81 @@
 
     @Test
     public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
+        initEthernetNetworkFactory();
         createInterfaceUndergoingProvisioning(TEST_IFACE);
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
         // verify that the IpClient gets shut down when interface state changes to down.
-        assertTrue(mNetFactory.updateInterfaceLinkState(TEST_IFACE, false));
+        final boolean ret =
+                mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+
+        assertTrue(ret);
         verify(mIpClient).shutdown();
+        assertSuccessfulListener(listener, null);
     }
 
     @Test
     public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
-        assertTrue(mNetFactory.updateInterfaceLinkState(TEST_IFACE, false));
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+        final boolean ret =
+                mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+
+        assertTrue(ret);
         verifyStop();
+        assertSuccessfulListener(listener, mMockNetwork);
     }
 
     @Test
     public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
+        initEthernetNetworkFactory();
         createUnprovisionedInterface(TEST_IFACE);
-        assertTrue(mNetFactory.updateInterfaceLinkState(TEST_IFACE, false));
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+        final boolean ret =
+                mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+
+        assertTrue(ret);
         // There should not be an active IPClient or NetworkAgent.
         verify(mDeps, never()).makeIpClient(any(), any(), any());
         verify(mDeps, never())
                 .makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
+        assertSuccessfulListener(listener, null);
     }
 
     @Test
     public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
+        initEthernetNetworkFactory();
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
         // if interface was never added, link state cannot be updated.
-        assertFalse(mNetFactory.updateInterfaceLinkState("eth1", true));
-        verify(mDeps, never()).makeIpClient(any(), any(), any());
+        final boolean ret =
+                mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
+
+        assertFalse(ret);
+        verifyNoStopOrStart();
+        assertFailedListener(listener, "can't be updated as it is not available");
+    }
+
+    @Test
+    public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
+        initEthernetNetworkFactory();
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+        final boolean ret =
+                mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
+
+        assertFalse(ret);
+        verifyNoStopOrStart();
+        assertFailedListener(listener, "No changes");
     }
 
     @Test
     public void testNeedNetworkForOnProvisionedInterface() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
         mNetFactory.needNetworkFor(createDefaultRequest());
         verify(mIpClient, never()).startProvisioning(any());
@@ -338,6 +389,7 @@
 
     @Test
     public void testNeedNetworkForOnUnprovisionedInterface() throws Exception {
+        initEthernetNetworkFactory();
         createUnprovisionedInterface(TEST_IFACE);
         mNetFactory.needNetworkFor(createDefaultRequest());
         verify(mIpClient).startProvisioning(any());
@@ -348,6 +400,7 @@
 
     @Test
     public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception {
+        initEthernetNetworkFactory();
         createInterfaceUndergoingProvisioning(TEST_IFACE);
         mNetFactory.needNetworkFor(createDefaultRequest());
         verify(mIpClient, never()).startProvisioning(any());
@@ -358,6 +411,7 @@
 
     @Test
     public void testProvisioningLoss() throws Exception {
+        initEthernetNetworkFactory();
         when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
         createAndVerifyProvisionedInterface(TEST_IFACE);
 
@@ -369,20 +423,30 @@
 
     @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());
+        verifyNoStopOrStart();
+    }
+
+    private void verifyNoStopOrStart() {
+        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);
+        mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER);
 
         mNetFactory.needNetworkFor(createDefaultRequest());
 
@@ -392,13 +456,15 @@
                 .build();
         mNetFactory.needNetworkFor(specificNetRequest);
 
-        // TODO(b/155707957): BUG: IPClient should not be started when the interface link state
-        //  is down.
-        verify(mDeps).makeIpClient(any(), any(), any());
+        verify(mDeps, never()).makeIpClient(any(), any(), any());
+
+        mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER);
+        verify(mDeps).makeIpClient(any(), eq(TEST_IFACE), any());
     }
 
     @Test
     public void testLinkPropertiesChanged() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
 
         LinkProperties lp = new LinkProperties();
@@ -409,6 +475,7 @@
 
     @Test
     public void testNetworkUnwanted() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
 
         mNetworkAgent.getCallbacks().onNetworkUnwanted();
@@ -418,6 +485,7 @@
 
     @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 +509,7 @@
 
     @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 +530,7 @@
 
     @Test
     public void testReachabilityLoss() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
 
         triggerOnReachabilityLost();
@@ -469,6 +539,72 @@
         verifyRestart(createDefaultIpConfig());
     }
 
+    @Test
+    public void testIgnoreOnIpLayerStartedCallbackAfterIpClientHasStopped() throws Exception {
+        initEthernetNetworkFactory();
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        mIpClientCallbacks.onProvisioningFailure(new LinkProperties());
+        mIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
+        mLooper.dispatchAll();
+        verifyStop();
+
+        // ipClient has been shut down first, we should not retry
+        verify(mIpClient, never()).startProvisioning(any());
+        verify(mNetworkAgent, never()).register();
+    }
+
+    @Test
+    public void testIgnoreOnIpLayerStoppedCallbackAfterIpClientHasStopped() throws Exception {
+        initEthernetNetworkFactory();
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
+        mIpClientCallbacks.onProvisioningFailure(new LinkProperties());
+        mIpClientCallbacks.onProvisioningFailure(new LinkProperties());
+        mLooper.dispatchAll();
+        verifyStop();
+
+        // ipClient has been shut down first, we should not retry
+        verify(mIpClient).startProvisioning(any());
+    }
+
+    @Test
+    public void testIgnoreLinkPropertiesCallbackAfterIpClientHasStopped() throws Exception {
+        initEthernetNetworkFactory();
+        createAndVerifyProvisionedInterface(TEST_IFACE);
+        LinkProperties lp = new LinkProperties();
+
+        // 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, NULL_LISTENER));
+        mIpClientCallbacks.onLinkPropertiesChange(lp);
+        mLooper.dispatchAll();
+
+        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);
+
+        // 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, NULL_LISTENER));
+        mIpClientCallbacks.onReachabilityLost("Neighbor Lost");
+        mLooper.dispatchAll();
+
+        verifyStop();
+        // ipClient has been shut down first, we should not update
+        verify(mIpClient, never()).startProvisioning(any());
+        verify(mNetworkAgent, never()).register();
+    }
+
     private void verifyRestart(@NonNull final IpConfiguration ipConfig) {
         verifyStop();
         verifyStart(ipConfig);
@@ -492,17 +628,17 @@
     }
 
     private static final class TestNetworkManagementListener
-            implements IInternalNetworkManagementListener {
-        private final CompletableFuture<Pair<Network, InternalNetworkManagementException>> mDone
+            implements IEthernetNetworkManagementListener {
+        private final CompletableFuture<Pair<Network, EthernetNetworkManagementException>> mDone
                 = new CompletableFuture<>();
 
         @Override
         public void onComplete(final Network network,
-                final InternalNetworkManagementException exception) {
+                final EthernetNetworkManagementException exception) {
             mDone.complete(new Pair<>(network, exception));
         }
 
-        Pair<Network, InternalNetworkManagementException> expectOnComplete() throws Exception {
+        Pair<Network, EthernetNetworkManagementException> expectOnComplete() throws Exception {
             return mDone.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
 
@@ -514,6 +650,7 @@
 
     @Test
     public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
         final NetworkCapabilities capabilities = createDefaultFilterCaps();
         final IpConfiguration ipConfiguration = createStaticIpConfig();
@@ -522,13 +659,86 @@
         mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
         triggerOnProvisioningSuccess();
 
-        final Pair<Network, InternalNetworkManagementException> ret = listener.expectOnComplete();
-        assertEquals(mMockNetwork, ret.first);
-        assertNull(ret.second);
+        assertSuccessfulListener(listener, mMockNetwork);
+    }
+
+    @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, NULL_LISTENER));
+    }
+
+    @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();
+                });
+
+        assertSuccessfulListener(successfulListener, mMockNetwork);
+    }
+
+    private void assertSuccessfulListener(
+            @NonNull final TestNetworkManagementListener successfulListener,
+            @NonNull final Network expectedNetwork) throws Exception {
+        final Pair<Network, EthernetNetworkManagementException> successfulResult =
+                successfulListener.expectOnComplete();
+        assertEquals(expectedNetwork, successfulResult.first);
+        assertNull(successfulResult.second);
+    }
+
+    private void assertFailedListener(@NonNull final TestNetworkManagementListener failedListener,
+            @NonNull final String errMsg)
+            throws Exception {
+        final Pair<Network, EthernetNetworkManagementException> failedResult =
+                failedListener.expectOnComplete();
+        assertNull(failedResult.first);
+        assertNotNull(failedResult.second);
+        assertTrue(failedResult.second.getMessage().contains(errMsg));
+    }
+
+    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();
+
+        assertFailedListener(failedListener, "aborted");
     }
 
     @Test
     public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception {
+        initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);
         final NetworkCapabilities capabilities = createDefaultFilterCaps();
         final IpConfiguration ipConfiguration = createStaticIpConfig();
diff --git a/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
index 1c08883..0ac28c4 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -18,15 +18,18 @@
 
 import static org.junit.Assert.assertThrows;
 
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.IInternalNetworkManagementListener;
-import android.net.InternalNetworkUpdateRequest;
+import android.net.IEthernetNetworkManagementListener;
+import android.net.EthernetNetworkUpdateRequest;
 import android.net.IpConfiguration;
 import android.net.NetworkCapabilities;
 import android.net.StaticIpConfiguration;
@@ -45,10 +48,10 @@
 @SmallTest
 public class EthernetServiceImplTest {
     private static final String TEST_IFACE = "test123";
-    private static final InternalNetworkUpdateRequest UPDATE_REQUEST =
-            new InternalNetworkUpdateRequest(
+    private static final EthernetNetworkUpdateRequest UPDATE_REQUEST =
+            new EthernetNetworkUpdateRequest(
                     new StaticIpConfiguration(), new NetworkCapabilities.Builder().build());
-    private static final IInternalNetworkManagementListener NULL_LISTENER = null;
+    private static final IEthernetNetworkManagementListener NULL_LISTENER = null;
     private EthernetServiceImpl mEthernetServiceImpl;
     @Mock private Context mContext;
     @Mock private Handler mHandler;
@@ -65,6 +68,15 @@
         shouldTrackIface(TEST_IFACE, true);
     }
 
+    private void toggleAutomotiveFeature(final boolean isEnabled) {
+        doReturn(isEnabled)
+                .when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) {
+        doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface);
+    }
+
     @Test
     public void testSetConfigurationRejectsWhenEthNotStarted() {
         mEthernetServiceImpl.mStarted.set(false);
@@ -143,11 +155,6 @@
         });
     }
 
-    private void toggleAutomotiveFeature(final boolean isEnabled) {
-        doReturn(isEnabled)
-                .when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-    }
-
     @Test
     public void testUpdateConfigurationRejectsWithUntrackedIface() {
         shouldTrackIface(TEST_IFACE, false);
@@ -172,8 +179,34 @@
         });
     }
 
-    private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) {
-        doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface);
+    private void denyManageEthPermission() {
+        doThrow(new SecurityException("")).when(mContext)
+                .enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_ETHERNET_NETWORKS), anyString());
+    }
+
+    @Test
+    public void testUpdateConfigurationRejectsWithoutManageEthPermission() {
+        denyManageEthPermission();
+        assertThrows(SecurityException.class, () -> {
+            mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+        });
+    }
+
+    @Test
+    public void testConnectNetworkRejectsWithoutManageEthPermission() {
+        denyManageEthPermission();
+        assertThrows(SecurityException.class, () -> {
+            mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+        });
+    }
+
+    @Test
+    public void testDisconnectNetworkRejectsWithoutManageEthPermission() {
+        denyManageEthPermission();
+        assertThrows(SecurityException.class, () -> {
+            mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+        });
     }
 
     @Test
@@ -184,4 +217,16 @@
                 eq(UPDATE_REQUEST.getIpConfig()),
                 eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER));
     }
+
+    @Test
+    public void testConnectNetwork() {
+        mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+        verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+    }
+
+    @Test
+    public void testDisconnectNetwork() {
+        mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+        verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), 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 5aca2e4..14f34d0 100644
--- a/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/ethernet/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -19,14 +19,18 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+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;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.InetAddresses;
-import android.net.IInternalNetworkManagementListener;
+import android.net.IEthernetNetworkManagementListener;
 import android.net.INetd;
 import android.net.IpConfiguration;
 import android.net.IpConfiguration.IpAssignment;
@@ -55,8 +59,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class EthernetTrackerTest {
+    private static final String TEST_IFACE = "test123";
     private static final int TIMEOUT_MS = 1_000;
     private static final String THREAD_NAME = "EthernetServiceThread";
+    private static final IEthernetNetworkManagementListener NULL_LISTENER = null;
     private EthernetTracker tracker;
     private HandlerThread mHandlerThread;
     @Mock private Context mContext;
@@ -68,6 +74,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         initMockResources();
+        when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean(), any())).thenReturn(false);
         mHandlerThread = new HandlerThread(THREAD_NAME);
         mHandlerThread.start();
         tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd);
@@ -294,16 +301,15 @@
 
     @Test
     public void testCreateEthernetTrackerConfigReturnsCorrectValue() {
-        final String iface = "1";
         final String capabilities = "2";
         final String ipConfig = "3";
         final String transport = "4";
-        final String configString = String.join(";", iface, capabilities, ipConfig, transport);
+        final String configString = String.join(";", TEST_IFACE, capabilities, ipConfig, transport);
 
         final EthernetTracker.EthernetTrackerConfig config =
                 EthernetTracker.createEthernetTrackerConfig(configString);
 
-        assertEquals(iface, config.mIface);
+        assertEquals(TEST_IFACE, config.mIface);
         assertEquals(capabilities, config.mCapabilities);
         assertEquals(ipConfig, config.mIpConfig);
         assertEquals(transport, config.mTransport);
@@ -317,16 +323,33 @@
 
     @Test
     public void testUpdateConfiguration() {
-        final String iface = "testiface";
         final NetworkCapabilities capabilities = new NetworkCapabilities.Builder().build();
         final StaticIpConfiguration staticIpConfig = new StaticIpConfiguration();
-        final IInternalNetworkManagementListener listener = null;
+        final IEthernetNetworkManagementListener listener = null;
 
-        tracker.updateConfiguration(iface, staticIpConfig, capabilities, listener);
+        tracker.updateConfiguration(TEST_IFACE, staticIpConfig, capabilities, listener);
         waitForIdle();
 
         verify(mFactory).updateInterface(
-                eq(iface), eq(EthernetTracker.createIpConfiguration(staticIpConfig)),
+                eq(TEST_IFACE), eq(EthernetTracker.createIpConfiguration(staticIpConfig)),
                 eq(capabilities), eq(listener));
     }
+
+    @Test
+    public void testConnectNetworkCorrectlyCallsFactory() {
+        tracker.connectNetwork(TEST_IFACE, NULL_LISTENER);
+        waitForIdle();
+
+        verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
+                eq(NULL_LISTENER));
+    }
+
+    @Test
+    public void testDisconnectNetworkCorrectlyCallsFactory() {
+        tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+        waitForIdle();
+
+        verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
+                eq(NULL_LISTENER));
+    }
 }