Merge changes from topic "refactor-pac" into main

* changes:
  Update unit tests to reflect the changes to RoutingCoordinator and PrivateAddressCoordinator
  Move PrivateAddressCoordinator from Tethering to staticlibs
  Refator IpServer to allocate IPv4 address via RoutingCoordinatorManager API
  Introduce PrivateAddressCoordinator methods at RoutingCoordinatorManager
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 7a6df88..f60d7a1 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -70,13 +70,13 @@
 import com.android.internal.util.State;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.IIpv4PrefixRequest;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.RoutingCoordinatorManager;
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.SyncStateMachine.StateInfo;
 import com.android.net.module.util.ip.InterfaceController;
 import com.android.networkstack.tethering.BpfCoordinator;
-import com.android.networkstack.tethering.PrivateAddressCoordinator;
 import com.android.networkstack.tethering.TetheringConfiguration;
 import com.android.networkstack.tethering.metrics.TetheringMetrics;
 import com.android.networkstack.tethering.util.InterfaceSet;
@@ -242,9 +242,10 @@
     private final BpfCoordinator mBpfCoordinator;
     @NonNull
     private final RoutingCoordinatorManager mRoutingCoordinator;
+    @NonNull
+    private final IIpv4PrefixRequest mIpv4PrefixRequest;
     private final Callback mCallback;
     private final InterfaceController mInterfaceCtrl;
-    private final PrivateAddressCoordinator mPrivateAddressCoordinator;
 
     private final String mIfaceName;
     private final int mInterfaceType;
@@ -301,7 +302,7 @@
             String ifaceName, Handler handler, int interfaceType, SharedLog log,
             INetd netd, @NonNull BpfCoordinator bpfCoordinator,
             RoutingCoordinatorManager routingCoordinatorManager, Callback callback,
-            TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
+            TetheringConfiguration config,
             TetheringMetrics tetheringMetrics, Dependencies deps) {
         super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
         mHandler = handler;
@@ -309,6 +310,12 @@
         mNetd = netd;
         mBpfCoordinator = bpfCoordinator;
         mRoutingCoordinator = routingCoordinatorManager;
+        mIpv4PrefixRequest = new IIpv4PrefixRequest.Stub() {
+            @Override
+            public void onIpv4PrefixConflict(IpPrefix ipPrefix) throws RemoteException {
+                sendMessage(CMD_NOTIFY_PREFIX_CONFLICT);
+            }
+        };
         mCallback = callback;
         mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
         mIfaceName = ifaceName;
@@ -317,7 +324,6 @@
         mUsingLegacyDhcp = config.useLegacyDhcpServer();
         mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
         mIsWifiP2pDedicatedIpEnabled = config.shouldEnableWifiP2pDedicatedIp();
-        mPrivateAddressCoordinator = addressCoordinator;
         mDeps = deps;
         mTetheringMetrics = tetheringMetrics;
         resetLinkProperties();
@@ -395,6 +401,11 @@
         return mInterfaceParams;
     }
 
+    @VisibleForTesting
+    public IIpv4PrefixRequest getIpv4PrefixRequest() {
+        return mIpv4PrefixRequest;
+    }
+
     /**
      * Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
      * thread.
@@ -643,7 +654,7 @@
         // NOTE: All of configureIPv4() will be refactored out of existence
         // into calls to InterfaceController, shared with startIPv4().
         mInterfaceCtrl.clearIPv4Address();
-        mPrivateAddressCoordinator.releaseDownstream(this);
+        mRoutingCoordinator.releaseDownstream(mIpv4PrefixRequest);
         mBpfCoordinator.tetherOffloadClientClear(this);
         mIpv4Address = null;
         mStaticIpv4ServerAddr = null;
@@ -714,7 +725,8 @@
 
         if (shouldUseWifiP2pDedicatedIp()) return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
 
-        return mPrivateAddressCoordinator.requestDownstreamAddress(this, scope, useLastAddress);
+        return mRoutingCoordinator.requestDownstreamAddress(mInterfaceType, scope, useLastAddress,
+                mIpv4PrefixRequest);
     }
 
     private boolean startIPv6() {
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 13b8004..df255f3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -253,7 +253,6 @@
     private final TetheringNotificationUpdater mNotificationUpdater;
     private final UserManager mUserManager;
     private final BpfCoordinator mBpfCoordinator;
-    private final PrivateAddressCoordinator mPrivateAddressCoordinator;
     private final TetheringMetrics mTetheringMetrics;
     private final WearableConnectionManager mWearableConnectionManager;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
@@ -359,7 +358,6 @@
         // Load tethering configuration.
         updateConfiguration();
         mConfig.readEnableSyncSM(mContext);
-        mPrivateAddressCoordinator = mDeps.makePrivateAddressCoordinator(mContext);
 
         // Must be initialized after tethering configuration is loaded because BpfCoordinator
         // constructor needs to use the configuration.
@@ -2001,11 +1999,11 @@
             final UpstreamNetworkState ns = (UpstreamNetworkState) o;
             switch (arg1) {
                 case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
-                    mPrivateAddressCoordinator.updateUpstreamPrefix(
+                    mRoutingCoordinator.updateUpstreamPrefix(
                             ns.linkProperties, ns.networkCapabilities, ns.network);
                     break;
                 case UpstreamNetworkMonitor.EVENT_ON_LOST:
-                    mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
+                    mRoutingCoordinator.removeUpstreamPrefix(ns.network);
                     break;
             }
 
@@ -2075,7 +2073,7 @@
                     return;
                 }
 
-                mPrivateAddressCoordinator.maybeRemoveDeprecatedUpstreams();
+                mRoutingCoordinator.maybeRemoveDeprecatedUpstreams();
                 mUpstreamNetworkMonitor.startObserveAllNetworks();
 
                 // TODO: De-duplicate with updateUpstreamWanted() below.
@@ -2663,11 +2661,6 @@
 
         dumpBpf(pw);
 
-        pw.println("Private address coordinator:");
-        pw.increaseIndent();
-        mPrivateAddressCoordinator.dump(pw);
-        pw.decreaseIndent();
-
         if (mWearableConnectionManager != null) {
             pw.println("WearableConnectionManager:");
             pw.increaseIndent();
@@ -2821,8 +2814,7 @@
         mLog.i("adding IpServer for: " + iface);
         final TetherState tetherState = new TetherState(
                 new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
-                        mRoutingCoordinator, new ControlCallback(), mConfig,
-                        mPrivateAddressCoordinator, mTetheringMetrics,
+                        mRoutingCoordinator, new ControlCallback(), mConfig, mTetheringMetrics,
                         mDeps.makeIpServerDependencies()), isNcm);
         mTetherStates.put(iface, tetherState);
         tetherState.ipServer.start();
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index cc878d5..a4823ca 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -36,6 +36,7 @@
 import androidx.annotation.RequiresApi;
 
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.PrivateAddressCoordinator;
 import com.android.net.module.util.RoutingCoordinatorManager;
 import com.android.net.module.util.RoutingCoordinatorService;
 import com.android.net.module.util.SharedLog;
@@ -136,7 +137,10 @@
     public RoutingCoordinatorManager getRoutingCoordinator(Context context, SharedLog log) {
         IBinder binder;
         if (!SdkLevel.isAtLeastS()) {
-            binder = new RoutingCoordinatorService(getINetd(context, log));
+            final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+            binder =
+                    new RoutingCoordinatorService(
+                            getINetd(context, log), cm::getAllNetworks, context);
         } else {
             binder = ConnectivityInternalApiUtil.getRoutingCoordinator(context);
         }
@@ -175,14 +179,6 @@
     }
 
     /**
-     * Make PrivateAddressCoordinator to be used by Tethering.
-     */
-    public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx) {
-        final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
-        return new PrivateAddressCoordinator(cm::getAllNetworks, ctx);
-    }
-
-    /**
      * Make BluetoothPanShim object to enable/disable bluetooth tethering.
      *
      * TODO: use BluetoothPan directly when mainline module is built with API 32.
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f7834a3..8f5e6c4 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -89,13 +89,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.RoutingCoordinatorManager;
-import com.android.net.module.util.SdkUtil.LateSdk;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.tethering.BpfCoordinator;
-import com.android.networkstack.tethering.PrivateAddressCoordinator;
 import com.android.networkstack.tethering.TetheringConfiguration;
 import com.android.networkstack.tethering.metrics.TetheringMetrics;
 import com.android.networkstack.tethering.util.InterfaceSet;
@@ -175,7 +172,6 @@
     @Mock private DadProxy mDadProxy;
     @Mock private RouterAdvertisementDaemon mRaDaemon;
     @Mock private IpServer.Dependencies mDependencies;
-    @Mock private PrivateAddressCoordinator mAddressCoordinator;
     @Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
     @Mock private NetworkStatsManager mStatsManager;
     @Mock private TetheringConfiguration mTetherConfig;
@@ -261,9 +257,9 @@
             verify(mBpfCoordinator).updateIpv6UpstreamInterface(
                     mIpServer, interfaceParams.index, upstreamPrefixes);
         }
-        reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator);
-        when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
-                anyBoolean())).thenReturn(mTestAddress);
+        reset(mNetd, mBpfCoordinator, mCallback, mRoutingCoordinatorManager);
+        when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
+                anyBoolean(), any())).thenReturn(mTestAddress);
     }
 
     @SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to Thread.run()
@@ -284,8 +280,8 @@
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
-        when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
-                anyBoolean())).thenReturn(mTestAddress);
+        when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
+                anyBoolean(), any())).thenReturn(mTestAddress);
         when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
         when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
 
@@ -297,7 +293,7 @@
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         return new IpServer(IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
-                mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator,
+                mRoutingCoordinatorManager, mCallback, mTetherConfig,
                 mTetheringMetrics, mDependencies);
 
     }
@@ -349,10 +345,14 @@
         initStateMachine(TETHERING_BLUETOOTH);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+        InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
         if (isAtLeastT()) {
-            inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
-                    eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
+            inOrder.verify(mRoutingCoordinatorManager)
+                    .requestDownstreamAddress(
+                            eq(TETHERING_BLUETOOTH),
+                            eq(CONNECTIVITY_SCOPE_GLOBAL),
+                            eq(true),
+                            any());
             inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                     IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
         }
@@ -373,7 +373,7 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+        InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
@@ -384,7 +384,7 @@
                     argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN)));
         }
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0));
-        inOrder.verify(mAddressCoordinator).releaseDownstream(any());
+        inOrder.verify(mRoutingCoordinatorManager).releaseDownstream(any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
@@ -392,7 +392,7 @@
         verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH),
                 eq(TETHER_ERROR_NO_ERROR));
         verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH));
-        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
+        verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
     }
 
     @Test
@@ -400,9 +400,9 @@
         initStateMachine(TETHERING_USB);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
-                eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
+        InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
+        inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
+                eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true), any());
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                 IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -414,7 +414,7 @@
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
+        verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
     }
 
     @Test
@@ -422,9 +422,9 @@
         initStateMachine(TETHERING_WIFI_P2P);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
-        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
-                eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
+        InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
+        inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
+                eq(CONNECTIVITY_SCOPE_LOCAL), eq(true), any());
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                   IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -436,7 +436,7 @@
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
+        verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
     }
 
     @Test
@@ -445,11 +445,11 @@
                 true /* shouldEnableWifiP2pDedicatedIp */);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
-        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+        InOrder inOrder = inOrder(mCallback, mNetd, mRoutingCoordinatorManager);
         // When using WiFi P2p dedicated IP, the IpServer just picks the IP address without
-        // requesting for it at PrivateAddressCoordinator.
-        inOrder.verify(mAddressCoordinator, never()).requestDownstreamAddress(any(), anyInt(),
-                anyBoolean());
+        // requesting for it at RoutingCoordinatorManager.
+        inOrder.verify(mRoutingCoordinatorManager, never())
+                .requestDownstreamAddress(anyInt(), anyInt(), anyBoolean(), any());
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                 IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -463,7 +463,7 @@
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
         assertEquals(List.of(new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS)),
                 mLinkPropertiesCaptor.getValue().getLinkAddresses());
-        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
+        verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager);
     }
 
     @Test
@@ -569,15 +569,9 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         clearInvocations(
-                mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
+                mNetd, mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder =
-                inOrder(
-                        mNetd,
-                        mCallback,
-                        mAddressCoordinator,
-                        mBpfCoordinator,
-                        mRoutingCoordinatorManager);
+        InOrder inOrder = inOrder(mNetd, mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
         inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mRoutingCoordinatorManager)
                 .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
@@ -592,15 +586,14 @@
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
                 argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
-        inOrder.verify(mAddressCoordinator).releaseDownstream(any());
+        inOrder.verify(mRoutingCoordinatorManager).releaseDownstream(any());
         inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
         inOrder.verify(mBpfCoordinator).removeIpServer(mIpServer);
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(
-                mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
+        verifyNoMoreInteractions(mNetd, mCallback, mRoutingCoordinatorManager, mBpfCoordinator);
     }
 
     @Test
@@ -737,9 +730,9 @@
 
         final ArgumentCaptor<LinkProperties> lpCaptor =
                 ArgumentCaptor.forClass(LinkProperties.class);
-        InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
-                eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
+        InOrder inOrder = inOrder(mNetd, mCallback, mRoutingCoordinatorManager);
+        inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
+                eq(CONNECTIVITY_SCOPE_LOCAL), eq(true), any());
         inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         // One for ipv4 route, one for ipv6 link local route.
         inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -747,18 +740,18 @@
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
-        verifyNoMoreInteractions(mCallback, mAddressCoordinator);
+        verifyNoMoreInteractions(mCallback, mRoutingCoordinatorManager);
 
         // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
         // onNewPrefixRequest callback.
         final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
-        when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
-                anyBoolean())).thenReturn(newAddress);
+        when(mRoutingCoordinatorManager.requestDownstreamAddress(anyInt(), anyInt(),
+                anyBoolean(), any())).thenReturn(newAddress);
         eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
         mLooper.dispatchAll();
 
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
-                eq(CONNECTIVITY_SCOPE_LOCAL), eq(false));
+        inOrder.verify(mRoutingCoordinatorManager).requestDownstreamAddress(anyInt(),
+                eq(CONNECTIVITY_SCOPE_LOCAL), eq(false), any());
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
         verifyNoMoreInteractions(mCallback);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index bff1fda..1ab5766 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -24,8 +24,9 @@
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.ip.IpServer.CMD_NOTIFY_PREFIX_CONFLICT;
 
-import static com.android.networkstack.tethering.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
+import static com.android.net.module.util.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
 import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
 
 import static org.junit.Assert.assertEquals;
@@ -34,6 +35,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -47,10 +51,14 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.ip.IpServer;
+import android.os.IBinder;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.IIpv4PrefixRequest;
+import com.android.net.module.util.PrivateAddressCoordinator;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -92,12 +100,26 @@
             new IpPrefix("172.16.0.0/12"),
             new IpPrefix("10.0.0.0/8")));
 
+    private void setUpIpServer(IpServer ipServer, int interfaceType) throws Exception {
+        when(ipServer.interfaceType()).thenReturn(interfaceType);
+        final IIpv4PrefixRequest request = mock(IIpv4PrefixRequest.class);
+        when(ipServer.getIpv4PrefixRequest()).thenReturn(request);
+        when(request.asBinder()).thenReturn(mock(IBinder.class));
+        doAnswer(
+                        invocation -> {
+                            ipServer.sendMessage(CMD_NOTIFY_PREFIX_CONFLICT);
+                            return null;
+                        })
+                .when(request)
+                .onIpv4PrefixConflict(any());
+    }
+
     private void setUpIpServers() throws Exception {
-        when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
-        when(mEthernetIpServer.interfaceType()).thenReturn(TETHERING_ETHERNET);
-        when(mHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
-        when(mLocalHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
-        when(mWifiP2pIpServer.interfaceType()).thenReturn(TETHERING_WIFI_P2P);
+        setUpIpServer(mUsbIpServer, TETHERING_USB);
+        setUpIpServer(mEthernetIpServer, TETHERING_ETHERNET);
+        setUpIpServer(mHotspotIpServer, TETHERING_WIFI);
+        setUpIpServer(mLocalHotspotIpServer, TETHERING_WIFI);
+        setUpIpServer(mWifiP2pIpServer, TETHERING_WIFI_P2P);
     }
 
     @Before
@@ -112,14 +134,22 @@
                 spy(new PrivateAddressCoordinator(mConnectivityMgr::getAllNetworks, mDeps));
     }
 
-    private LinkAddress requestDownstreamAddress(final IpServer ipServer, int scope,
-            boolean useLastAddress) {
-        final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                ipServer, scope, useLastAddress);
+    private LinkAddress requestDownstreamAddress(
+            final IpServer ipServer, int scope, boolean useLastAddress) throws Exception {
+        final LinkAddress address =
+                mPrivateAddressCoordinator.requestDownstreamAddress(
+                        ipServer.interfaceType(),
+                        scope,
+                        useLastAddress,
+                        ipServer.getIpv4PrefixRequest());
         when(ipServer.getAddress()).thenReturn(address);
         return address;
     }
 
+    private void releaseDownstream(final IpServer ipServer) {
+        mPrivateAddressCoordinator.releaseDownstream(ipServer.getIpv4PrefixRequest());
+    }
+
     private void updateUpstreamPrefix(UpstreamNetworkState ns) {
         mPrivateAddressCoordinator.updateUpstreamPrefix(
                 ns.linkProperties, ns.networkCapabilities, ns.network);
@@ -145,8 +175,8 @@
         assertNotEquals(usbPrefix, bluetoothPrefix);
         assertNotEquals(usbPrefix, newHotspotPrefix);
 
-        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
-        mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+        releaseDownstream(mHotspotIpServer);
+        releaseDownstream(mUsbIpServer);
     }
 
     @Test
@@ -158,7 +188,7 @@
                 CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
         assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
-        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+        releaseDownstream(mHotspotIpServer);
 
         // - Test previous enabled hotspot prefix(cached prefix) is reserved.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@@ -168,7 +198,7 @@
         final IpPrefix usbPrefix = asIpPrefix(usbAddress);
         assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
         assertNotEquals(hotspotPrefix, usbPrefix);
-        mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+        releaseDownstream(mUsbIpServer);
 
         // - Test wifi p2p prefix is reserved.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@@ -179,7 +209,7 @@
         assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
         assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
         assertNotEquals(hotspotPrefix, etherPrefix);
-        mPrivateAddressCoordinator.releaseDownstream(mEthernetIpServer);
+        releaseDownstream(mEthernetIpServer);
     }
 
     @Test
@@ -190,8 +220,8 @@
         final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
                 CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
 
-        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
-        mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+        releaseDownstream(mHotspotIpServer);
+        releaseDownstream(mUsbIpServer);
 
         final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
                 CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
@@ -254,10 +284,11 @@
     }
 
     private void verifyNotifyConflictAndRelease(final IpServer ipServer) throws Exception {
-        verify(ipServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
-        mPrivateAddressCoordinator.releaseDownstream(ipServer);
+        verify(ipServer).sendMessage(CMD_NOTIFY_PREFIX_CONFLICT);
+        releaseDownstream(ipServer);
+        final int interfaceType = ipServer.interfaceType();
         reset(ipServer);
-        setUpIpServers();
+        setUpIpServer(ipServer, interfaceType);
     }
 
     private int getSubAddress(final byte... ipv4Address) {
@@ -273,7 +304,7 @@
         final IpPrefix hotspotPrefix = asIpPrefix(address);
         final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
         assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
-        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+        releaseDownstream(mHotspotIpServer);
     }
 
     @Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 66fe957..d0c036f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -71,7 +71,6 @@
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
-import static com.android.networkstack.tethering.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
 import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
 import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
 import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
@@ -97,6 +96,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
@@ -192,7 +192,9 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.PrivateAddressCoordinator;
 import com.android.net.module.util.RoutingCoordinatorManager;
+import com.android.net.module.util.RoutingCoordinatorService;
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.ip.IpNeighborMonitor;
 import com.android.networkstack.apishim.common.BluetoothPanShim;
@@ -293,7 +295,6 @@
     @Mock private BluetoothPanShim mBluetoothPanShim;
     @Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
     @Mock private TetheringMetrics mTetheringMetrics;
-    @Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
     @Mock private PrivateAddressCoordinator.Dependencies mPrivateAddressCoordinatorDependencies;
 
     private final MockIpServerDependencies mIpServerDependencies =
@@ -318,12 +319,12 @@
     private TetheringConfiguration mConfig;
     private EntitlementManager mEntitleMgr;
     private OffloadController mOffloadCtrl;
-    private PrivateAddressCoordinator mPrivateAddressCoordinator;
     private SoftApCallback mSoftApCallback;
     private SoftApCallback mLocalOnlyHotspotCallback;
     private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     private UpstreamNetworkMonitor.EventListener mEventListener;
     private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
+    private RoutingCoordinatorManager mRoutingCoordinatorManager;
 
     private TestConnectivityManager mCm;
     private boolean mForceEthernetServiceUnavailable = false;
@@ -489,8 +490,16 @@
         }
 
         @Override
-        public RoutingCoordinatorManager getRoutingCoordinator(final Context context,
-                SharedLog log) {
+        public RoutingCoordinatorManager getRoutingCoordinator(
+                final Context context, SharedLog log) {
+            ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+            when(mPrivateAddressCoordinatorDependencies.isFeatureEnabled(anyString()))
+                    .thenReturn(false);
+            RoutingCoordinatorService service = new RoutingCoordinatorService(
+                    getINetd(context, log),
+                            cm::getAllNetworks,
+                            mPrivateAddressCoordinatorDependencies);
+            mRoutingCoordinatorManager = spy(new RoutingCoordinatorManager(context, service));
             return mRoutingCoordinatorManager;
         }
 
@@ -537,15 +546,6 @@
         }
 
         @Override
-        public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx) {
-            ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
-            mPrivateAddressCoordinator =
-                    new PrivateAddressCoordinator(
-                            cm::getAllNetworks, mPrivateAddressCoordinatorDependencies);
-            return mPrivateAddressCoordinator;
-        }
-
-        @Override
         public BluetoothPanShim makeBluetoothPanShim(BluetoothPan pan) {
             try {
                 when(mBluetoothPanShim.requestTetheredInterface(
@@ -668,8 +668,6 @@
                 .thenReturn(true);
         initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 0 /* defaultDisabled */);
         when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
-        when(mPrivateAddressCoordinatorDependencies.isFeatureEnabled(anyString()))
-                .thenReturn(false);
 
         mServiceContext = new TestContext(mContext);
         mServiceContext.setUseRegisteredHandlers(true);
@@ -687,6 +685,7 @@
                 new IntentFilter(ACTION_TETHER_STATE_CHANGED));
 
         mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class)));
+        when(mCm.getAllNetworks()).thenReturn(new Network[] {});
 
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
@@ -870,6 +869,9 @@
             assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
         }
 
+        // Ignore calls to {@link ConnectivityManager#getallNetworks}.
+        verify(mCm, atLeast(0)).getAllNetworks();
+
         // The default network request is only ever filed once.
         verifyNoMoreInteractions(mCm);
     }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index cb62ae1..a04ebdd 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -2028,7 +2028,8 @@
             mCdmps = null;
         }
 
-        mRoutingCoordinatorService = new RoutingCoordinatorService(netd);
+        mRoutingCoordinatorService =
+                new RoutingCoordinatorService(netd, this::getAllNetworks, mContext);
         mMulticastRoutingCoordinatorService =
                 mDeps.makeMulticastRoutingCoordinatorService(mHandler);
 
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 9d27608..66e1dad 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -650,6 +650,7 @@
     name: "net-utils-all-srcs",
     srcs: [
         "device/**/*.java",
+        ":framework-connectivity-shared-srcs",
         ":net-utils-framework-common-srcs",
     ],
     visibility: ["//visibility:private"],
diff --git a/staticlibs/device/com/android/net/module/util/IIpv4PrefixRequest.aidl b/staticlibs/device/com/android/net/module/util/IIpv4PrefixRequest.aidl
new file mode 100644
index 0000000..cc1c19c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/IIpv4PrefixRequest.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+
+/** @hide */
+// TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the class from being
+// jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder invocation
+// to an incorrect interface" when calling the IPC.
+@Descriptor("value=no.jarjar.com.android.net.module.util.IIpv4PrefixRequest")
+interface IIpv4PrefixRequest {
+    void onIpv4PrefixConflict(in IpPrefix ipPrefix);
+}
diff --git a/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
index 72a4a94..097824f 100644
--- a/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
+++ b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
@@ -16,8 +16,14 @@
 
 package com.android.net.module.util;
 
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.RouteInfo;
 
+import com.android.net.module.util.IIpv4PrefixRequest;
+
 /** @hide */
 // TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the DESCRIPTOR from
 // being jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder
@@ -96,4 +102,34 @@
     *         cause of the failure.
     */
     void removeInterfaceForward(in String fromIface, in String toIface);
+
+    /** Update the prefix of an upstream. */
+    void updateUpstreamPrefix(in @nullable LinkProperties lp,
+                              in @nullable NetworkCapabilities nc,
+                              in Network network);
+
+    /** Remove the upstream prefix of the given {@link Network}. */
+    void removeUpstreamPrefix(in Network network);
+
+    /** Remove the deprecated upstream networks if any. */
+    void maybeRemoveDeprecatedUpstreams();
+
+   /**
+    * Request an IPv4 address for the downstream.
+    *
+    * @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
+    * @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
+    * @param useLastAddress whether to use the last address
+    * @param request a {@link IIpv4PrefixRequest} to report conflicts
+    * @return an IPv4 address allocated for the downstream, could be null
+    */
+    @nullable
+    LinkAddress requestDownstreamAddress(
+            in int interfaceType,
+            in int scope,
+            in boolean useLastAddress,
+            in IIpv4PrefixRequest request);
+
+    /** Release the IPv4 address allocated for the downstream. */
+    void releaseDownstream(in IIpv4PrefixRequest request);
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
similarity index 82%
rename from Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
rename to staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
index 50f82cf..990358d 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/staticlibs/device/com/android/net/module/util/PrivateAddressCoordinator.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.networkstack.tethering;
+package com.android.net.module.util;
 
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
@@ -24,7 +24,6 @@
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
-import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
 
 import static java.util.Arrays.asList;
 
@@ -34,16 +33,13 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.ip.IpServer;
+import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.net.module.util.DeviceConfigUtils;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -51,6 +47,8 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
 import java.util.function.Supplier;
@@ -62,7 +60,7 @@
  * coordinator is responsible for recording all of network assigned addresses and dispatched
  * free address to downstream interfaces.
  *
- * This class is not thread-safe and should be accessed on the same tethering internal thread.
+ * This class is not thread-safe.
  * @hide
  */
 public class PrivateAddressCoordinator {
@@ -78,7 +76,9 @@
     // when tethering is down. Instead tethering would remove all deprecated upstreams from
     // mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprecatedUpstreams().
     private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
-    private final ArraySet<IpServer> mDownstreams;
+    // The downstreams are indexed by Ipv4PrefixRequest, which is a wrapper of the Binder object of
+    // IIpv4PrefixRequest.
+    private final ArrayMap<Ipv4PrefixRequest, Downstream> mDownstreams;
     private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
     private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
     private final List<IpPrefix> mTetheringPrefixes;
@@ -116,7 +116,7 @@
     @VisibleForTesting
     public PrivateAddressCoordinator(Supplier<Network[]> getAllNetworksSupplier,
                                      Dependencies deps) {
-        mDownstreams = new ArraySet<>();
+        mDownstreams = new ArrayMap<>();
         mUpstreamPrefixMap = new ArrayMap<>();
         mGetAllNetworksSupplier = getAllNetworksSupplier;
         mDeps = deps;
@@ -168,12 +168,18 @@
     }
 
     private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
-        for (IpServer downstream : mDownstreams) {
-            final IpPrefix target = getDownstreamPrefix(downstream);
+        for (Map.Entry<Ipv4PrefixRequest, Downstream> entry : mDownstreams.entrySet()) {
+            final Ipv4PrefixRequest request = entry.getKey();
+            final Downstream downstream = entry.getValue();
+            final IpPrefix target = asIpPrefix(downstream.getAddress());
 
             for (IpPrefix source : prefixes) {
                 if (isConflictPrefix(source, target)) {
-                    downstream.sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+                    try {
+                        request.getRequest().onIpv4PrefixConflict(target);
+                    } catch (RemoteException ignored) {
+                        // ignore
+                    }
                     break;
                 }
             }
@@ -199,21 +205,26 @@
         mUpstreamPrefixMap.removeAll(toBeRemoved);
     }
 
+    // TODO: There needs to be a reserveDownstreamAddress() method for the cases where
+    // TetheringRequest has been set a static IPv4 address.
+
     /**
      * Pick a random available address and mark its prefix as in use for the provided IpServer,
      * returns null if there is no available address.
      */
     @Nullable
-    public LinkAddress requestDownstreamAddress(final IpServer ipServer, final int scope,
-            boolean useLastAddress) {
-        final AddressKey addrKey = new AddressKey(ipServer.interfaceType(), scope);
+    public LinkAddress requestDownstreamAddress(int interfaceType, final int scope,
+            boolean useLastAddress,
+            IIpv4PrefixRequest request) {
+        final Ipv4PrefixRequest wrappedRequest = new Ipv4PrefixRequest(request);
+        final AddressKey addrKey = new AddressKey(interfaceType, scope);
         // This ensures that tethering isn't started on 2 different interfaces with the same type.
         // Once tethering could support multiple interface with the same type,
         // TetheringSoftApCallback would need to handle it among others.
         final LinkAddress cachedAddress = mCachedAddresses.get(addrKey);
         if (useLastAddress && cachedAddress != null
                 && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
-            mDownstreams.add(ipServer);
+            mDownstreams.put(wrappedRequest, new Downstream(interfaceType, cachedAddress));
             return cachedAddress;
         }
 
@@ -223,7 +234,7 @@
                     (prefixIndex + i) % mTetheringPrefixes.size());
             final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
             if (newAddress != null) {
-                mDownstreams.add(ipServer);
+                mDownstreams.put(wrappedRequest, new Downstream(interfaceType, newAddress));
                 mCachedAddresses.put(addrKey, newAddress);
                 return newAddress;
             }
@@ -327,8 +338,8 @@
     }
 
     /** Release downstream record for IpServer. */
-    public void releaseDownstream(final IpServer ipServer) {
-        mDownstreams.remove(ipServer);
+    public void releaseDownstream(IIpv4PrefixRequest request) {
+        mDownstreams.remove(new Ipv4PrefixRequest(request));
     }
 
     /** Clear current upstream prefixes records. */
@@ -368,8 +379,8 @@
 
         // IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
         // in mCachedAddresses.
-        for (IpServer downstream : mDownstreams) {
-            final IpPrefix target = getDownstreamPrefix(downstream);
+        for (Downstream downstream : mDownstreams.values()) {
+            final IpPrefix target = asIpPrefix(downstream.getAddress());
 
             if (isConflictPrefix(prefix, target)) return target;
         }
@@ -377,11 +388,51 @@
         return null;
     }
 
-    @NonNull
-    private IpPrefix getDownstreamPrefix(final IpServer downstream) {
-        final LinkAddress address = downstream.getAddress();
+    private static IpPrefix asIpPrefix(LinkAddress addr) {
+        return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
+    }
 
-        return asIpPrefix(address);
+    private static final class Ipv4PrefixRequest {
+        private final IIpv4PrefixRequest mRequest;
+
+        Ipv4PrefixRequest(IIpv4PrefixRequest request) {
+            mRequest = request;
+        }
+
+        public IIpv4PrefixRequest getRequest() {
+            return mRequest;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (!(obj instanceof Ipv4PrefixRequest)) return false;
+            return Objects.equals(
+                    mRequest.asBinder(), ((Ipv4PrefixRequest) obj).mRequest.asBinder());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(mRequest.asBinder());
+        }
+    }
+
+    private static final class Downstream {
+        private final int mInterfaceType;
+        private final LinkAddress mAddress;
+
+        private Downstream(int interfaceType, LinkAddress address) {
+            mInterfaceType = interfaceType;
+            mAddress = address;
+        }
+
+        public int getInterfaceType() {
+            return mInterfaceType;
+        }
+
+        public LinkAddress getAddress() {
+            return mAddress;
+        }
     }
 
     private static class AddressKey {
@@ -412,6 +463,7 @@
         }
     }
 
+    // TODO: dump PrivateAddressCoordinator when dumping RoutingCoordinatorService.
     void dump(final IndentingPrintWriter pw) {
         pw.println("mTetheringPrefixes:");
         pw.increaseIndent();
@@ -429,8 +481,8 @@
 
         pw.println("mDownstreams:");
         pw.increaseIndent();
-        for (IpServer ipServer : mDownstreams) {
-            pw.println(ipServer.interfaceType() + " - " + ipServer.getAddress());
+        for (Downstream downstream : mDownstreams.values()) {
+            pw.println(downstream.getInterfaceType() + " - " + downstream.getAddress());
         }
         pw.decreaseIndent();
 
diff --git a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
index 02e3643..9ea0947 100644
--- a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
@@ -17,17 +17,27 @@
 package com.android.net.module.util;
 
 import android.content.Context;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.RouteInfo;
 import android.os.IBinder;
 import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 /**
  * A manager class for talking to the routing coordinator service.
  *
  * This class should only be used by the connectivity and tethering module. This is enforced
  * by the build rules. Do not change build rules to gain access to this class from elsewhere.
+ *
+ * This class has following functionalities:
+ * - Manage routes and forwarding for networks.
+ * - Manage IPv4 prefix allocation for network interfaces.
+ *
  * @hide
  */
 public class RoutingCoordinatorManager {
@@ -154,4 +164,65 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    // PrivateAddressCoordinator methods:
+
+    /** Update the prefix of an upstream. */
+    public void updateUpstreamPrefix(LinkProperties lp, NetworkCapabilities nc, Network network) {
+        try {
+            mService.updateUpstreamPrefix(lp, nc, network);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Remove the upstream prefix of the given {@link Network}. */
+    public void removeUpstreamPrefix(Network network) {
+        try {
+            mService.removeUpstreamPrefix(network);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Remove the deprecated upstream networks if any. */
+    public void maybeRemoveDeprecatedUpstreams() {
+        try {
+            mService.maybeRemoveDeprecatedUpstreams();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request an IPv4 address for the downstream.
+     *
+     * @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
+     * @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
+     * @param useLastAddress whether to use the last address
+     * @param request a {@link IIpv4PrefixRequest} to report conflicts
+     * @return an IPv4 address allocated for the downstream, could be null
+     */
+    @Nullable
+    public LinkAddress requestDownstreamAddress(
+            int interfaceType,
+            int scope,
+            boolean useLastAddress,
+            IIpv4PrefixRequest request) {
+        try {
+            return mService.requestDownstreamAddress(
+                    interfaceType, scope, useLastAddress, request);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Release the IPv4 address allocated for the downstream. */
+    public void releaseDownstream(IIpv4PrefixRequest request) {
+        try {
+            mService.releaseDownstream(request);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
index c75b860..d16c234 100644
--- a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
@@ -19,8 +19,13 @@
 import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.net.INetd;
 
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.RouteInfo;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -28,8 +33,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Objects;
+import java.util.function.Supplier;
 
 /**
  * Class to coordinate routing across multiple clients.
@@ -45,8 +52,22 @@
     private static final String TAG = RoutingCoordinatorService.class.getSimpleName();
     private final INetd mNetd;
 
-    public RoutingCoordinatorService(@NonNull INetd netd) {
+    private final Object mPrivateAddressCoordinatorLock = new Object();
+    @GuardedBy("mPrivateAddressCoordinatorLock")
+    private final PrivateAddressCoordinator mPrivateAddressCoordinator;
+
+    public RoutingCoordinatorService(@NonNull INetd netd,
+                                     @NonNull Supplier<Network[]> getAllNetworksSupplier,
+                                     @NonNull Context context) {
+        this(netd, getAllNetworksSupplier, new PrivateAddressCoordinator.Dependencies(context));
+    }
+
+    @VisibleForTesting
+    public RoutingCoordinatorService(@NonNull INetd netd,
+                                     @NonNull Supplier<Network[]> getAllNetworksSupplier,
+                                     @NonNull PrivateAddressCoordinator.Dependencies pacDeps) {
         mNetd = netd;
+        mPrivateAddressCoordinator = new PrivateAddressCoordinator(getAllNetworksSupplier, pacDeps);
     }
 
     /**
@@ -225,4 +246,74 @@
             }
         }
     }
+
+    // PrivateAddressCoordinator methods:
+
+    /** Update the prefix of an upstream. */
+    @Override
+    public void updateUpstreamPrefix(LinkProperties lp, NetworkCapabilities nc, Network network) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    synchronized (mPrivateAddressCoordinatorLock) {
+                        mPrivateAddressCoordinator.updateUpstreamPrefix(lp, nc, network);
+                    }
+                });
+    }
+
+    /** Remove the upstream prefix of the given {@link Network}. */
+    @Override
+    public void removeUpstreamPrefix(Network network) {
+        Objects.requireNonNull(network);
+        BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    synchronized (mPrivateAddressCoordinatorLock) {
+                        mPrivateAddressCoordinator.removeUpstreamPrefix(network);
+                    }
+                });
+    }
+
+    /** Remove the deprecated upstream networks if any. */
+    @Override
+    public void maybeRemoveDeprecatedUpstreams() {
+        BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    synchronized (mPrivateAddressCoordinatorLock) {
+                        mPrivateAddressCoordinator.maybeRemoveDeprecatedUpstreams();
+                    }
+                });
+    }
+
+    /**
+     * Request an IPv4 address for the downstream.
+     *
+     * @param interfaceType the Tethering type (see TetheringManager#TETHERING_*).
+     * @param scope CONNECTIVITY_SCOPE_GLOBAL or CONNECTIVITY_SCOPE_LOCAL
+     * @param useLastAddress whether to use the last address
+     * @param request a {@link IIpv4PrefixRequest} to report conflicts
+     * @return an IPv4 address allocated for the downstream, could be null
+     */
+    @Override
+    public LinkAddress requestDownstreamAddress(int interfaceType, int scope,
+            boolean useLastAddress, IIpv4PrefixRequest request) {
+        Objects.requireNonNull(request);
+        return BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    synchronized (mPrivateAddressCoordinatorLock) {
+                        return mPrivateAddressCoordinator.requestDownstreamAddress(
+                                interfaceType, scope, useLastAddress, request);
+                    }
+                });
+    }
+
+    /** Release the IPv4 address allocated for the downstream. */
+    @Override
+    public void releaseDownstream(IIpv4PrefixRequest request) {
+        Objects.requireNonNull(request);
+        BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    synchronized (mPrivateAddressCoordinatorLock) {
+                        mPrivateAddressCoordinator.releaseDownstream(request);
+                    }
+                });
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
index b04561c..035ce0f 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.net.module.util
 
+import android.content.Context
 import android.net.INetd
+import android.net.Network
 import android.os.Build
 import android.util.Log
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -34,7 +36,9 @@
 @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
 class RoutingCoordinatorServiceTest {
     val mNetd = mock(INetd::class.java)
-    val mService = RoutingCoordinatorService(mNetd)
+    val mGetAllNetworksSupplier = { emptyArray<Network>() }
+    val mContext = mock(Context::class.java)
+    val mService = RoutingCoordinatorService(mNetd, mGetAllNetworksSupplier, mContext)
 
     @Test
     fun testInterfaceForward() {