Merge "Import translations. DO NOT MERGE ANYWHERE" into udc-dev
diff --git a/framework/src/android/net/NetworkInfo.java b/framework/src/android/net/NetworkInfo.java
index b7ec519..7aa9847 100644
--- a/framework/src/android/net/NetworkInfo.java
+++ b/framework/src/android/net/NetworkInfo.java
@@ -334,6 +334,24 @@
     }
 
     /**
+     * Indicates whether this network is suspended.
+     * @deprecated Apps should instead use the
+     *             {@link android.net.ConnectivityManager.NetworkCallback} API to
+     *             learn about connectivity changes. See
+     *             {@link ConnectivityManager#registerDefaultNetworkCallback} and
+     *             {@link ConnectivityManager#registerNetworkCallback}. These will
+     *             give a more accurate picture of the connectivity state of
+     *             the device and let apps react more easily and quickly to changes.
+     * @hide
+     */
+    @Deprecated
+    public boolean isSuspended() {
+        synchronized (this) {
+            return mState == State.SUSPENDED;
+        }
+    }
+
+    /**
      * Indicates whether network connectivity is possible. A network is unavailable
      * when a persistent or semi-persistent condition prevents the possibility
      * of connecting to that network. Examples include
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 6455044..2281c81 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -154,13 +154,13 @@
                     }
 
                     @Override
-                    public void onSocketDestroyed(@Nullable Network network) {
+                    public void onAllSocketsDestroyed(@Nullable Network network) {
                         synchronized (MdnsDiscoveryManager.this) {
                             final MdnsServiceTypeClient serviceTypeClient =
                                     perNetworkServiceTypeClients.get(serviceType, network);
                             if (serviceTypeClient == null) return;
                             // Notify all listeners that all services are removed from this socket.
-                            serviceTypeClient.notifyAllServicesRemoved();
+                            serviceTypeClient.notifySocketDestroyed();
                             perNetworkServiceTypeClients.remove(serviceTypeClient);
                         }
                     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 6414453..52c8622 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -34,7 +34,6 @@
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
 import java.util.List;
-import java.util.Map;
 
 /**
  * The {@link MdnsMultinetworkSocketClient} manages the multinetwork socket for mDns
@@ -48,9 +47,8 @@
     @NonNull private final Handler mHandler;
     @NonNull private final MdnsSocketProvider mSocketProvider;
 
-    private final Map<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
+    private final ArrayMap<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
             new ArrayMap<>();
-    private final ArrayMap<MdnsInterfaceSocket, Network> mActiveNetworkSockets = new ArrayMap<>();
     private final ArrayMap<MdnsInterfaceSocket, ReadPacketHandler> mSocketPacketHandlers =
             new ArrayMap<>();
     private MdnsSocketClientBase.Callback mCallback = null;
@@ -63,7 +61,11 @@
     }
 
     private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
+        @NonNull
         private final SocketCreationCallback mSocketCreationCallback;
+        @NonNull
+        private final ArrayMap<MdnsInterfaceSocket, Network> mActiveNetworkSockets =
+                new ArrayMap<>();
 
         InterfaceSocketCallback(SocketCreationCallback socketCreationCallback) {
             mSocketCreationCallback = socketCreationCallback;
@@ -88,10 +90,59 @@
         @Override
         public void onInterfaceDestroyed(@Nullable Network network,
                 @NonNull MdnsInterfaceSocket socket) {
-            mSocketPacketHandlers.remove(socket);
-            mActiveNetworkSockets.remove(socket);
-            mSocketCreationCallback.onSocketDestroyed(network);
+            notifySocketDestroyed(socket);
+            maybeCleanupPacketHandler(socket);
         }
+
+        private void notifySocketDestroyed(@NonNull MdnsInterfaceSocket socket) {
+            final Network network = mActiveNetworkSockets.remove(socket);
+            if (!isAnySocketActive(network)) {
+                mSocketCreationCallback.onAllSocketsDestroyed(network);
+            }
+        }
+
+        void onNetworkUnrequested() {
+            for (int i = mActiveNetworkSockets.size() - 1; i >= 0; i--) {
+                // Iterate from the end so the socket can be removed
+                final MdnsInterfaceSocket socket = mActiveNetworkSockets.keyAt(i);
+                notifySocketDestroyed(socket);
+                maybeCleanupPacketHandler(socket);
+            }
+        }
+    }
+
+    private boolean isSocketActive(@NonNull MdnsInterfaceSocket socket) {
+        for (int i = 0; i < mRequestedNetworks.size(); i++) {
+            final InterfaceSocketCallback isc = mRequestedNetworks.valueAt(i);
+            if (isc.mActiveNetworkSockets.containsKey(socket)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isAnySocketActive(@Nullable Network network) {
+        for (int i = 0; i < mRequestedNetworks.size(); i++) {
+            final InterfaceSocketCallback isc = mRequestedNetworks.valueAt(i);
+            if (isc.mActiveNetworkSockets.containsValue(network)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private ArrayMap<MdnsInterfaceSocket, Network> getActiveSockets() {
+        final ArrayMap<MdnsInterfaceSocket, Network> sockets = new ArrayMap<>();
+        for (int i = 0; i < mRequestedNetworks.size(); i++) {
+            final InterfaceSocketCallback isc = mRequestedNetworks.valueAt(i);
+            sockets.putAll(isc.mActiveNetworkSockets);
+        }
+        return sockets;
+    }
+
+    private void maybeCleanupPacketHandler(@NonNull MdnsInterfaceSocket socket) {
+        if (isSocketActive(socket)) return;
+        mSocketPacketHandlers.remove(socket);
     }
 
     private class ReadPacketHandler implements MulticastPacketReader.PacketHandler {
@@ -143,11 +194,14 @@
     @Override
     public void notifyNetworkUnrequested(@NonNull MdnsServiceBrowserListener listener) {
         ensureRunningOnHandlerThread(mHandler);
-        final InterfaceSocketCallback callback = mRequestedNetworks.remove(listener);
+        final InterfaceSocketCallback callback = mRequestedNetworks.get(listener);
         if (callback == null) {
             Log.e(TAG, "Can not be unrequested with unknown listener=" + listener);
             return;
         }
+        callback.onNetworkUnrequested();
+        // onNetworkUnrequested does cleanups based on mRequestedNetworks, only remove afterwards
+        mRequestedNetworks.remove(listener);
         mSocketProvider.unrequestSocket(callback);
     }
 
@@ -156,9 +210,10 @@
                 instanceof Inet6Address;
         final boolean isIpv4 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
                 instanceof Inet4Address;
-        for (int i = 0; i < mActiveNetworkSockets.size(); i++) {
-            final MdnsInterfaceSocket socket = mActiveNetworkSockets.keyAt(i);
-            final Network network = mActiveNetworkSockets.valueAt(i);
+        final ArrayMap<MdnsInterfaceSocket, Network> activeSockets = getActiveSockets();
+        for (int i = 0; i < activeSockets.size(); i++) {
+            final MdnsInterfaceSocket socket = activeSockets.keyAt(i);
+            final Network network = activeSockets.valueAt(i);
             // Check ip capability and network before sending packet
             if (((isIpv6 && socket.hasJoinedIpv6()) || (isIpv4 && socket.hasJoinedIpv4()))
                     && isNetworkMatched(targetNetwork, network)) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 4e6571f..e56bd5b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -284,7 +284,7 @@
     }
 
     /** Notify all services are removed because the socket is destroyed. */
-    public void notifyAllServicesRemoved() {
+    public void notifySocketDestroyed() {
         synchronized (lock) {
             for (MdnsResponse response : instanceNameToResponse.values()) {
                 final String name = response.getServiceInstanceName();
@@ -302,6 +302,11 @@
                     listener.onServiceNameRemoved(serviceInfo);
                 }
             }
+
+            if (requestTaskFuture != null) {
+                requestTaskFuture.cancel(true);
+                requestTaskFuture = null;
+            }
         }
     }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
index 6bcad01..c614cd3 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
@@ -88,6 +88,6 @@
         void onSocketCreated(@Nullable Network network);
 
         /*** Notify requested socket is destroyed */
-        void onSocketDestroyed(@Nullable Network network);
+        void onAllSocketsDestroyed(@Nullable Network network);
     }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index ca61d34..e245ff1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -599,8 +599,6 @@
             if (matchRequestedNetwork(network)) continue;
             final SocketInfo info = mNetworkSockets.removeAt(i);
             info.mSocket.destroy();
-            // Still notify to unrequester for socket destroy.
-            cb.onInterfaceDestroyed(network, info.mSocket);
             mSharedLog.log("Remove socket on net:" + network + " after unrequestSocket");
         }
 
@@ -610,8 +608,6 @@
         for (int i = mTetherInterfaceSockets.size() - 1; i >= 0; i--) {
             final SocketInfo info = mTetherInterfaceSockets.valueAt(i);
             info.mSocket.destroy();
-            // Still notify to unrequester for socket destroy.
-            cb.onInterfaceDestroyed(null /* network */, info.mSocket);
             mSharedLog.log("Remove socket on ifName:" + mTetherInterfaceSockets.keyAt(i)
                     + " after unrequestSocket");
         }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8ec6eb9..c667d72 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -2663,7 +2663,8 @@
         final ArrayList<NetworkStateSnapshot> result = new ArrayList<>();
         for (Network network : getAllNetworks()) {
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-            if (nai != null && nai.everConnected()) {
+            final boolean includeNetwork = (nai != null) && nai.isCreated();
+            if (includeNetwork) {
                 // TODO (b/73321673) : NetworkStateSnapshot contains a copy of the
                 // NetworkCapabilities, which may contain UIDs of apps to which the
                 // network applies. Should the UIDs be cleared so as not to leak or
@@ -3889,9 +3890,9 @@
                     break;
                 }
                 case NetworkAgent.EVENT_UNREGISTER_AFTER_REPLACEMENT: {
-                    if (!nai.isCreated()) {
-                        Log.d(TAG, "unregisterAfterReplacement on uncreated " + nai.toShortString()
-                                + ", tearing down instead");
+                    if (!nai.everConnected()) {
+                        Log.d(TAG, "unregisterAfterReplacement on never-connected "
+                                + nai.toShortString() + ", tearing down instead");
                         teardownUnneededNetwork(nai);
                         break;
                     }
@@ -4476,6 +4477,25 @@
         }
     }
 
+    @VisibleForTesting
+    protected static boolean shouldCreateNetworksImmediately() {
+        // Before U, physical networks are only created when the agent advances to CONNECTED.
+        // In U and above, all networks are immediately created when the agent is registered.
+        return SdkLevel.isAtLeastU();
+    }
+
+    private static boolean shouldCreateNativeNetwork(@NonNull NetworkAgentInfo nai,
+            @NonNull NetworkInfo.State state) {
+        if (nai.isCreated()) return false;
+        if (state == NetworkInfo.State.CONNECTED) return true;
+        if (state != NetworkInfo.State.CONNECTING) {
+            // TODO: throw if no WTFs are observed in the field.
+            Log.wtf(TAG, "Uncreated network in invalid state: " + state);
+            return false;
+        }
+        return nai.isVPN() || shouldCreateNetworksImmediately();
+    }
+
     private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
         return nai.isCreated() && !nai.isDestroyed();
     }
@@ -4526,7 +4546,7 @@
         // because they lost all their requests or because their score isn't good)
         // then they would disconnect organically, report their new state and then
         // disconnect the channel.
-        if (nai.networkInfo.isConnected()) {
+        if (nai.networkInfo.isConnected() || nai.networkInfo.isSuspended()) {
             nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                     null, null);
         }
@@ -7918,7 +7938,7 @@
 
         if (isDefaultNetwork(networkAgent)) {
             handleApplyDefaultProxy(newLp.getHttpProxy());
-        } else {
+        } else if (networkAgent.everConnected()) {
             updateProxy(newLp, oldLp);
         }
 
@@ -7952,6 +7972,10 @@
         mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
     }
 
+    private void applyInitialLinkProperties(@NonNull NetworkAgentInfo nai) {
+        updateLinkProperties(nai, new LinkProperties(nai.linkProperties), null);
+    }
+
     /**
      * @param naData captive portal data from NetworkAgent
      * @param apiData captive portal data from capport API
@@ -9714,21 +9738,32 @@
                     + oldInfo.getState() + " to " + state);
         }
 
-        if (!networkAgent.isCreated()
-                && (state == NetworkInfo.State.CONNECTED
-                || (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
-
+        if (shouldCreateNativeNetwork(networkAgent, state)) {
             // A network that has just connected has zero requests and is thus a foreground network.
             networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
 
             if (!createNativeNetwork(networkAgent)) return;
+
+            networkAgent.setCreated();
+
+            // If the network is created immediately on register, then apply the LinkProperties now.
+            // Otherwise, this is done further down when the network goes into connected state.
+            // Applying the LinkProperties means that the network is ready to carry traffic -
+            // interfaces and routing rules have been added, DNS servers programmed, etc.
+            // For VPNs, this must be done before the capabilities are updated, because as soon as
+            // that happens, UIDs are routed to the network.
+            if (shouldCreateNetworksImmediately()) {
+                applyInitialLinkProperties(networkAgent);
+            }
+
+            // TODO: should this move earlier? It doesn't seem to have anything to do with whether
+            // a network is created or not.
             if (networkAgent.propagateUnderlyingCapabilities()) {
                 // Initialize the network's capabilities to their starting values according to the
                 // underlying networks. This ensures that the capabilities are correct before
                 // anything happens to the network.
                 updateCapabilitiesForNetwork(networkAgent);
             }
-            networkAgent.setCreated();
             networkAgent.onNetworkCreated();
             updateAllowedUids(networkAgent, null, networkAgent.networkCapabilities);
             updateProfileAllowedNetworks();
@@ -9742,8 +9777,19 @@
             networkAgent.getAndSetNetworkCapabilities(networkAgent.networkCapabilities);
 
             handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
-            updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
-                    null);
+            if (!shouldCreateNetworksImmediately()) {
+                applyInitialLinkProperties(networkAgent);
+            } else {
+                // The network was created when the agent registered, and the LinkProperties are
+                // already up-to-date. However, updateLinkProperties also makes some changes only
+                // when the network connects. Apply those changes here. On T and below these are
+                // handled by the applyInitialLinkProperties call just above.
+                // TODO: stop relying on updateLinkProperties(..., null) to do this.
+                // If something depends on both LinkProperties and connected state, it should be in
+                // this method as well.
+                networkAgent.clatd.update();
+                updateProxy(networkAgent.linkProperties, null);
+            }
 
             // If a rate limit has been configured and is applicable to this network (network
             // provides internet connectivity), apply it. The tc police filter cannot be attached
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 50de714..f0ad3f1 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2130,7 +2130,12 @@
     @AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
     @Test
     public void testSetAirplaneMode() throws Exception{
-        final boolean supportWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI);
+        // Starting from T, wifi supports airplane mode enhancement which may not disconnect wifi
+        // when airplane mode is on. The actual behavior that the device will have could only be
+        // checked with hidden wifi APIs(see Settings.Secure.WIFI_APM_STATE). Thus, stop verifying
+        // wifi on T+ device.
+        final boolean verifyWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && !SdkLevel.isAtLeastT();
         final boolean supportTelephony = mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
         // store the current state of airplane mode
         final boolean isAirplaneModeEnabled = isAirplaneModeEnabled();
@@ -2141,7 +2146,7 @@
         // Verify that networks are available as expected if wifi or cell is supported. Continue the
         // test if none of them are supported since test should still able to verify the permission
         // mechanism.
-        if (supportWifi) {
+        if (verifyWifi) {
             mCtsNetUtils.ensureWifiConnected();
             registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
         }
@@ -2165,7 +2170,7 @@
             // Verify that the enabling airplane mode takes effect as expected to prevent flakiness
             // caused by fast airplane mode switches. Ensure network lost before turning off
             // airplane mode.
-            if (supportWifi) waitForLost(wifiCb);
+            if (verifyWifi) waitForLost(wifiCb);
             if (supportTelephony) waitForLost(telephonyCb);
 
             // Verify we can disable Airplane Mode with correct permission:
@@ -2174,7 +2179,7 @@
             // Verify that turning airplane mode off takes effect as expected.
             // connectToCell only registers a request, it cannot / does not need to be called twice
             mCtsNetUtils.ensureWifiConnected();
-            if (supportWifi) waitForAvailable(wifiCb);
+            if (verifyWifi) waitForAvailable(wifiCb);
             if (supportTelephony) waitForAvailable(telephonyCb);
         } finally {
             // Restore the previous state of airplane mode and permissions:
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 869562b..af8938a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -29,9 +29,9 @@
 import android.net.NattKeepalivePacketData
 import android.net.Network
 import android.net.NetworkAgent
-import android.net.NetworkAgentConfig
 import android.net.NetworkAgent.INVALID_NETWORK
 import android.net.NetworkAgent.VALID_NETWORK
+import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
@@ -46,21 +46,23 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_TEST
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkInfo
 import android.net.NetworkProvider
 import android.net.NetworkReleasedException
 import android.net.NetworkRequest
 import android.net.NetworkScore
-import android.net.RouteInfo
 import android.net.QosCallback
-import android.net.QosCallbackException
 import android.net.QosCallback.QosCallbackRegistrationException
+import android.net.QosCallbackException
 import android.net.QosSession
 import android.net.QosSessionAttributes
 import android.net.QosSocketInfo
+import android.net.RouteInfo
 import android.net.SocketKeepalive
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
 import android.net.Uri
 import android.net.VpnManager
 import android.net.VpnTransportInfo
@@ -71,6 +73,7 @@
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Message
+import android.os.Process
 import android.os.SystemClock
 import android.platform.test.annotations.AppModeFull
 import android.system.OsConstants.IPPROTO_TCP
@@ -89,6 +92,7 @@
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Losing
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
@@ -178,6 +182,7 @@
     private val agentsToCleanUp = mutableListOf<NetworkAgent>()
     private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
     private var qosTestSocket: Closeable? = null // either Socket or DatagramSocket
+    private val ifacesToCleanUp = mutableListOf<TestNetworkInterface>()
 
     @Before
     fun setUp() {
@@ -189,6 +194,7 @@
     fun tearDown() {
         agentsToCleanUp.forEach { it.unregister() }
         callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+        ifacesToCleanUp.forEach { it.fileDescriptor.close() }
         qosTestSocket?.close()
         mHandlerThread.quitSafely()
         mHandlerThread.join()
@@ -269,7 +275,7 @@
         removeCapability(NET_CAPABILITY_INTERNET)
         addCapability(NET_CAPABILITY_NOT_SUSPENDED)
         addCapability(NET_CAPABILITY_NOT_ROAMING)
-        addCapability(NET_CAPABILITY_NOT_VPN)
+        if (!transports.contains(TRANSPORT_VPN)) addCapability(NET_CAPABILITY_NOT_VPN)
         if (SdkLevel.isAtLeastS()) {
             addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
         }
@@ -304,7 +310,7 @@
         context: Context = realContext,
         specifier: String? = UUID.randomUUID().toString(),
         initialConfig: NetworkAgentConfig? = null,
-        expectedInitSignalStrengthThresholds: IntArray? = intArrayOf(),
+        expectedInitSignalStrengthThresholds: IntArray = intArrayOf(),
         transports: IntArray = intArrayOf()
     ): Pair<TestableNetworkAgent, TestableNetworkCallback> {
         val callback = TestableNetworkCallback()
@@ -317,8 +323,7 @@
         agent.register()
         agent.markConnected()
         agent.expectCallback<OnNetworkCreated>()
-        agent.expectSignalStrengths(expectedInitSignalStrengthThresholds)
-        agent.expectValidationBypassedStatus()
+        agent.expectPostConnectionCallbacks(expectedInitSignalStrengthThresholds)
         callback.expectAvailableThenValidatedCallbacks(agent.network!!)
         return agent to callback
     }
@@ -336,6 +341,19 @@
         mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
     }
 
+    private fun TestableNetworkAgent.expectPostConnectionCallbacks(
+        thresholds: IntArray = intArrayOf()
+    ) {
+        expectSignalStrengths(thresholds)
+        expectValidationBypassedStatus()
+        assertNoCallback()
+    }
+
+    private fun createTunInterface(): TestNetworkInterface = realContext.getSystemService(
+                TestNetworkManager::class.java)!!.createTunInterface(emptyList()).also {
+            ifacesToCleanUp.add(it)
+    }
+
     fun assertLinkPropertiesEventually(
         n: Network,
         description: String,
@@ -1291,8 +1309,12 @@
         requestNetwork(makeTestNetworkRequest(specifier = specifier6), callback)
         val agent6 = createNetworkAgent(specifier = specifier6)
         val network6 = agent6.register()
-        // No callbacks are sent, so check the LinkProperties to see if the network has connected.
-        assertLinkPropertiesEventuallyNotNull(agent6.network!!)
+        if (SdkLevel.isAtLeastU()) {
+            agent6.expectCallback<OnNetworkCreated>()
+        } else {
+            // No callbacks are sent, so check LinkProperties to wait for the network to be created.
+            assertLinkPropertiesEventuallyNotNull(agent6.network!!)
+        }
 
         // unregisterAfterReplacement tears down the network immediately.
         // Approximately check that this is the case by picking an unregister timeout that's longer
@@ -1301,8 +1323,9 @@
         val timeoutMs = agent6.DEFAULT_TIMEOUT_MS.toInt() + 1_000
         agent6.unregisterAfterReplacement(timeoutMs)
         agent6.expectCallback<OnNetworkUnwanted>()
-        if (!SdkLevel.isAtLeastT()) {
+        if (!SdkLevel.isAtLeastT() || SdkLevel.isAtLeastU()) {
             // Before T, onNetworkDestroyed is called even if the network was never created.
+            // On U+, the network was created by register(). Destroying it sends onNetworkDestroyed.
             agent6.expectCallback<OnNetworkDestroyed>()
         }
         // Poll for LinkProperties becoming null, because when onNetworkUnwanted is called, the
@@ -1375,4 +1398,101 @@
         callback.expect<Available>(agent.network!!)
         callback.eventuallyExpect<Lost> { it.network == agent.network }
     }
+
+    fun doTestNativeNetworkCreation(expectCreatedImmediately: Boolean, transports: IntArray) {
+        val iface = createTunInterface()
+        val ifName = iface.interfaceName
+        val nc = makeTestNetworkCapabilities(ifName, transports).also {
+            if (transports.contains(TRANSPORT_VPN)) {
+                val sessionId = "NetworkAgentTest-${Process.myPid()}"
+                it.transportInfo = VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, sessionId,
+                    /*bypassable=*/ false, /*longLivedTcpConnectionsExpensive=*/ false)
+                it.underlyingNetworks = listOf()
+            }
+        }
+        val lp = LinkProperties().apply {
+            interfaceName = ifName
+            addLinkAddress(LinkAddress("2001:db8::1/64"))
+            addRoute(RouteInfo(IpPrefix("2001:db8::/64"), null /* nextHop */, ifName))
+            addRoute(RouteInfo(IpPrefix("::/0"),
+                    InetAddresses.parseNumericAddress("fe80::abcd"),
+                    ifName))
+        }
+
+        // File a request containing the agent's specifier to receive callbacks and to ensure that
+        // the agent is not torn down due to being unneeded.
+        val request = makeTestNetworkRequest(specifier = ifName)
+        val requestCallback = TestableNetworkCallback()
+        requestNetwork(request, requestCallback)
+
+        val listenCallback = TestableNetworkCallback()
+        registerNetworkCallback(request, listenCallback)
+
+        // Register the NetworkAgent...
+        val agent = createNetworkAgent(realContext, initialNc = nc, initialLp = lp)
+        val network = agent.register()
+
+        // ... and then change the NetworkCapabilities and LinkProperties.
+        nc.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+        agent.sendNetworkCapabilities(nc)
+        lp.addLinkAddress(LinkAddress("192.0.2.2/25"))
+        lp.addRoute(RouteInfo(IpPrefix("192.0.2.0/25"), null /* nextHop */, ifName))
+        agent.sendLinkProperties(lp)
+
+        requestCallback.assertNoCallback()
+        listenCallback.assertNoCallback()
+        if (!expectCreatedImmediately) {
+            agent.assertNoCallback()
+            agent.markConnected()
+            agent.expectCallback<OnNetworkCreated>()
+        } else {
+            agent.expectCallback<OnNetworkCreated>()
+            agent.markConnected()
+        }
+        agent.expectPostConnectionCallbacks()
+
+        // onAvailable must be called only when the network connects, and no other callbacks may be
+        // called before that happens. The callbacks report the state of the network as it was when
+        // it connected, so they reflect the NC and LP changes made after registration.
+        requestCallback.expect<Available>(network)
+        listenCallback.expect<Available>(network)
+
+        requestCallback.expect<CapabilitiesChanged>(network) { it.caps.hasCapability(
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED) }
+        listenCallback.expect<CapabilitiesChanged>(network) { it.caps.hasCapability(
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED) }
+
+        requestCallback.expect<LinkPropertiesChanged>(network) { it.lp.equals(lp) }
+        listenCallback.expect<LinkPropertiesChanged>(network) { it.lp.equals(lp) }
+
+        requestCallback.expect<BlockedStatus>()
+        listenCallback.expect<BlockedStatus>()
+
+        // Except for network validation, ensure no more callbacks are sent.
+        requestCallback.expectCaps(network) {
+            it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        listenCallback.expectCaps(network) {
+            it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        unregister(agent)
+        // Lost implicitly checks that no further callbacks happened after connect.
+        requestCallback.expect<Lost>(network)
+        listenCallback.expect<Lost>(network)
+        assertNull(mCM.getLinkProperties(network))
+    }
+
+    @Test
+    fun testNativeNetworkCreation_PhysicalNetwork() {
+        // On T and below, the native network is only created when the agent connects.
+        // Starting in U, the native network is created as soon as the agent is registered.
+        doTestNativeNetworkCreation(expectCreatedImmediately = SdkLevel.isAtLeastU(),
+            intArrayOf(TRANSPORT_CELLULAR))
+    }
+
+    @Test
+    fun testNativeNetworkCreation_Vpn() {
+        // VPN networks are always created as soon as the agent is registered.
+        doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
+    }
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 8396252..e6ffecd 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -3815,6 +3815,12 @@
 
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, callbacks);
 
+        if (mService.shouldCreateNetworksImmediately()) {
+            assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } else {
+            assertNull(eventOrder.poll());
+        }
+
         // Connect a network, and file a request for it after it has come up, to ensure the nascent
         // timer is cleared and the test does not have to wait for it. Filing the request after the
         // network has come up is necessary because ConnectivityService does not appear to clear the
@@ -3822,7 +3828,12 @@
         // connected.
         // TODO: fix this bug, file the request before connecting, and remove the waitForIdle.
         mWiFiAgent.connectWithoutInternet();
-        waitForIdle();
+        if (!mService.shouldCreateNetworksImmediately()) {
+            assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } else {
+            waitForIdle();
+            assertNull(eventOrder.poll());
+        }
         mCm.requestNetwork(request, callback);
         callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
@@ -3839,7 +3850,6 @@
 
         // Disconnect the network and check that events happened in the right order.
         mCm.unregisterNetworkCallback(callback);
-        assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -7625,7 +7635,9 @@
         // Simple connection with initial LP should have updated ifaces.
         mCellAgent.connect(false);
         waitForIdle();
-        expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
+        List<Network> allNetworks = mService.shouldCreateNetworksImmediately()
+                ? cellAndWifi() : onlyCell();
+        expectNotifyNetworkStatus(allNetworks, onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
         // Verify change fields other than interfaces does not trigger a notification to NSS.
@@ -7934,9 +7946,13 @@
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
 
         mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        final int netId = mCellAgent.getNetwork().netId;
         waitForIdle();
-        verify(mMockDnsResolver, never()).setResolverConfiguration(any());
-        verifyNoMoreInteractions(mMockDnsResolver);
+        if (mService.shouldCreateNetworksImmediately()) {
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        } else {
+            verify(mMockDnsResolver, never()).setResolverConfiguration(any());
+        }
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -7952,10 +7968,13 @@
         mCellAgent.sendLinkProperties(cellLp);
         mCellAgent.connect(false);
         waitForIdle();
-
-        verify(mMockDnsResolver, times(1)).createNetworkCache(eq(mCellAgent.getNetwork().netId));
-        // CS tells dnsresolver about the empty DNS config for this network.
+        if (!mService.shouldCreateNetworksImmediately()) {
+            // CS tells dnsresolver about the empty DNS config for this network.
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        }
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
+
+        verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockDnsResolver);
 
         cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
@@ -8070,10 +8089,13 @@
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
         mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        final int netId = mCellAgent.getNetwork().netId;
         waitForIdle();
-        // CS tells netd about the empty DNS config for this network.
-        verify(mMockDnsResolver, never()).setResolverConfiguration(any());
-        verifyNoMoreInteractions(mMockDnsResolver);
+        if (mService.shouldCreateNetworksImmediately()) {
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        } else {
+            verify(mMockDnsResolver, never()).setResolverConfiguration(any());
+        }
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -8092,7 +8114,9 @@
         mCellAgent.sendLinkProperties(cellLp);
         mCellAgent.connect(false);
         waitForIdle();
-        verify(mMockDnsResolver, times(1)).createNetworkCache(eq(mCellAgent.getNetwork().netId));
+        if (!mService.shouldCreateNetworksImmediately()) {
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        }
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
                 mResolverParamsParcelCaptor.capture());
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
@@ -8103,6 +8127,7 @@
         assertEquals(2, resolvrParams.tlsServers.length);
         assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
                 asList("2001:db8::1", "192.0.2.1")));
+        verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockDnsResolver);
         cellNetworkCallback.expect(AVAILABLE, mCellAgent);
         cellNetworkCallback.expect(NETWORK_CAPS_UPDATED, mCellAgent);
@@ -10474,7 +10499,8 @@
         if (inOrder != null) {
             return inOrder.verify(t);
         } else {
-            return verify(t);
+            // times(1) for consistency with the above. InOrder#verify always implies times(1).
+            return verify(t, times(1));
         }
     }
 
@@ -10523,6 +10549,21 @@
         }
     }
 
+    private void expectNativeNetworkCreated(int netId, int permission, String iface,
+            InOrder inOrder) throws Exception {
+        verifyWithOrder(inOrder, mMockNetd).networkCreate(nativeNetworkConfigPhysical(netId,
+                permission));
+        verifyWithOrder(inOrder, mMockDnsResolver).createNetworkCache(eq(netId));
+        if (iface != null) {
+            verifyWithOrder(inOrder, mMockNetd).networkAddInterface(netId, iface);
+        }
+    }
+
+    private void expectNativeNetworkCreated(int netId, int permission, String iface)
+            throws Exception {
+        expectNativeNetworkCreated(netId, permission, iface, null /* inOrder */);
+    }
+
     @Test
     public void testStackedLinkProperties() throws Exception {
         final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
@@ -10560,11 +10601,8 @@
         int cellNetId = mCellAgent.getNetwork().netId;
         waitForIdle();
 
-        verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId,
-                INetd.PERMISSION_NONE));
+        expectNativeNetworkCreated(cellNetId, INetd.PERMISSION_NONE, MOBILE_IFNAME);
         assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default);
-        verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
-        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME);
         final ArrayTrackRecord<ReportedInterfaces>.ReadHead readHead =
                 mDeps.mReportedInterfaceHistory.newReadHead();
         assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(mServiceContext,
@@ -15111,7 +15149,7 @@
             UserHandle testHandle,
             TestNetworkCallback profileDefaultNetworkCallback,
             TestNetworkCallback disAllowProfileDefaultNetworkCallback) throws Exception {
-        final InOrder inOrder = inOrder(mMockNetd);
+        final InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver);
 
         mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellAgent.connect(true);
@@ -15127,8 +15165,16 @@
 
         final TestNetworkAgentWrapper workAgent =
                 makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
+        if (mService.shouldCreateNetworksImmediately()) {
+            expectNativeNetworkCreated(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM,
+                    null /* iface */, inOrder);
+        }
         if (connectWorkProfileAgentAhead) {
             workAgent.connect(false);
+            if (!mService.shouldCreateNetworksImmediately()) {
+                expectNativeNetworkCreated(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM,
+                        null /* iface */, inOrder);
+            }
         }
 
         final TestOnCompleteListener listener = new TestOnCompleteListener();
@@ -15168,6 +15214,11 @@
 
         if (!connectWorkProfileAgentAhead) {
             workAgent.connect(false);
+            if (!mService.shouldCreateNetworksImmediately()) {
+                inOrder.verify(mMockNetd).networkCreate(
+                        nativeNetworkConfigPhysical(workAgent.getNetwork().netId,
+                                INetd.PERMISSION_SYSTEM));
+            }
         }
 
         profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
@@ -15176,8 +15227,6 @@
         }
         mSystemDefaultNetworkCallback.assertNoCallback();
         mDefaultNetworkCallback.assertNoCallback();
-        inOrder.verify(mMockNetd).networkCreate(
-                nativeNetworkConfigPhysical(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM));
         inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                 workAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreference),
@@ -17696,6 +17745,22 @@
         verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
     }
 
+    private void verifyMtuSetOnWifiInterfaceOnlyUpToT(int mtu) throws Exception {
+        if (!mService.shouldCreateNetworksImmediately()) {
+            verify(mMockNetd, times(1)).interfaceSetMtu(WIFI_IFNAME, mtu);
+        } else {
+            verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+        }
+    }
+
+    private void verifyMtuSetOnWifiInterfaceOnlyStartingFromU(int mtu) throws Exception {
+        if (mService.shouldCreateNetworksImmediately()) {
+            verify(mMockNetd, times(1)).interfaceSetMtu(WIFI_IFNAME, mtu);
+        } else {
+            verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+        }
+    }
+
     @Test
     public void testSendLinkPropertiesSetInterfaceMtuBeforeConnect() throws Exception {
         final int mtu = 1281;
@@ -17710,8 +17775,8 @@
         reset(mMockNetd);
 
         mWiFiAgent.connect(false /* validated */);
-        // The MTU is always (re-)applied when the network connects.
-        verifyMtuSetOnWifiInterface(mtu);
+        // Before U, the MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterfaceOnlyUpToT(mtu);
     }
 
     @Test
@@ -17721,13 +17786,13 @@
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
-        // Registering an agent with an MTU doesn't set the MTU...
+        // Registering an agent with an MTU only sets the MTU on U+.
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
         waitForIdle();
-        verifyMtuNeverSetOnWifiInterface();
+        verifyMtuSetOnWifiInterfaceOnlyStartingFromU(mtu);
         reset(mMockNetd);
 
-        // ... but prevents future updates with the same MTU from setting the MTU.
+        // Future updates with the same MTU don't set the MTU even on T when it's not set initially.
         mWiFiAgent.sendLinkProperties(lp);
         waitForIdle();
         verifyMtuNeverSetOnWifiInterface();
@@ -17740,8 +17805,8 @@
         reset(mMockNetd);
 
         mWiFiAgent.connect(false /* validated */);
-        // The MTU is always (re-)applied when the network connects.
-        verifyMtuSetOnWifiInterface(mtu + 1);
+        // Before U, the MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterfaceOnlyUpToT(mtu + 1);
     }
 
     @Test
@@ -17922,4 +17987,33 @@
         verify(mDeps).destroyLiveTcpSockets(eq(UidRange.toIntRanges(ranges)),
                 eq(exemptUids));
     }
+
+    @Test
+    public void testDisconnectSuspendedNetworkStopClatd() throws Exception {
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_DUN)
+                .build();
+        mCm.requestNetwork(networkRequest, networkCallback);
+
+        final IpPrefix nat64Prefix = new IpPrefix(InetAddress.getByName("64:ff9b::"), 96);
+        NetworkCapabilities nc = new NetworkCapabilities().addCapability(NET_CAPABILITY_DUN);
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(MOBILE_IFNAME);
+        lp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
+        lp.setNat64Prefix(nat64Prefix);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, lp, nc);
+        mCellAgent.connect(true /* validated */, false /* hasInternet */,
+                false /* privateDnsProbeSent */);
+
+        verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, mCellAgent.getNetwork().netId,
+                nat64Prefix.toString());
+
+        mCellAgent.suspend();
+        mCm.unregisterNetworkCallback(networkCallback);
+        mCellAgent.expectDisconnected();
+        waitForIdle();
+
+        verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 63357f1..45da874 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -222,8 +222,8 @@
         // The client for NETWORK_1 receives the callback that the NETWORK_1 has been destroyed,
         // mockServiceTypeClientOne1 should send service removed notifications and remove from the
         // list of clients.
-        callback.onSocketDestroyed(NETWORK_1);
-        verify(mockServiceTypeClientOne1).notifyAllServicesRemoved();
+        callback.onAllSocketsDestroyed(NETWORK_1);
+        verify(mockServiceTypeClientOne1).notifySocketDestroyed();
 
         // Receive a response again, it should be processed only on mockServiceTypeClientTwo2.
         // Because the mockServiceTypeClientOne1 is removed from the list of clients, it is no
@@ -236,8 +236,8 @@
 
         // The client for NETWORK_2 receives the callback that the NETWORK_1 has been destroyed,
         // mockServiceTypeClientTwo2 shouldn't send any notifications.
-        callback2.onSocketDestroyed(NETWORK_1);
-        verify(mockServiceTypeClientTwo2, never()).notifyAllServicesRemoved();
+        callback2.onAllSocketsDestroyed(NETWORK_1);
+        verify(mockServiceTypeClientTwo2, never()).notifySocketDestroyed();
 
         // Receive a response again, mockServiceTypeClientTwo2 is still in the list of clients, it's
         // still able to process responses.
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 90c43e5..c6137c6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -24,8 +24,12 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.net.InetAddresses;
 import android.net.Network;
@@ -77,12 +81,17 @@
     }
 
     private SocketCallback expectSocketCallback() {
+        return expectSocketCallback(mListener, mNetwork);
+    }
+
+    private SocketCallback expectSocketCallback(MdnsServiceBrowserListener listener,
+            Network requestedNetwork) {
         final ArgumentCaptor<SocketCallback> callbackCaptor =
                 ArgumentCaptor.forClass(SocketCallback.class);
         mHandler.post(() -> mSocketClient.notifyNetworkRequested(
-                mListener, mNetwork, mSocketCreationCallback));
+                listener, requestedNetwork, mSocketCreationCallback));
         verify(mProvider, timeout(DEFAULT_TIMEOUT))
-                .requestSocket(eq(mNetwork), callbackCaptor.capture());
+                .requestSocket(eq(requestedNetwork), callbackCaptor.capture());
         return callbackCaptor.getValue();
     }
 
@@ -169,4 +178,103 @@
                         new String[] { "Android", "local" } /* serviceHost */)
         ), response.answers);
     }
+
+    @Test
+    public void testSocketRemovedAfterNetworkUnrequested() throws IOException {
+        // Request a socket
+        final SocketCallback callback = expectSocketCallback(mListener, mNetwork);
+        final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+                InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+        doReturn(true).when(mSocket).hasJoinedIpv4();
+        doReturn(true).when(mSocket).hasJoinedIpv6();
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        // Notify socket created
+        callback.onSocketCreated(mNetwork, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(mNetwork);
+
+        // Send IPv4 packet and verify sending has been called.
+        mSocketClient.sendMulticastPacket(ipv4Packet);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket).send(ipv4Packet);
+
+        // Request another socket with null network
+        final MdnsServiceBrowserListener listener2 = mock(MdnsServiceBrowserListener.class);
+        final Network network2 = mock(Network.class);
+        final MdnsInterfaceSocket socket2 = mock(MdnsInterfaceSocket.class);
+        final SocketCallback callback2 = expectSocketCallback(listener2, null);
+        doReturn(true).when(socket2).hasJoinedIpv4();
+        doReturn(true).when(socket2).hasJoinedIpv6();
+        doReturn(createEmptyNetworkInterface()).when(socket2).getInterface();
+        // Notify socket created for two networks.
+        callback2.onSocketCreated(mNetwork, mSocket, List.of());
+        callback2.onSocketCreated(network2, socket2, List.of());
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(mNetwork);
+        verify(mSocketCreationCallback).onSocketCreated(network2);
+
+        // Send IPv4 packet and verify sending to two sockets.
+        mSocketClient.sendMulticastPacket(ipv4Packet);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket, times(2)).send(ipv4Packet);
+        verify(socket2).send(ipv4Packet);
+
+        // Unrequest another socket
+        mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(listener2));
+        verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback2);
+
+        // Send IPv4 packet again and verify only sending via mSocket
+        mSocketClient.sendMulticastPacket(ipv4Packet);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket, times(3)).send(ipv4Packet);
+        verify(socket2).send(ipv4Packet);
+
+        // Unrequest remaining socket
+        mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(mListener));
+        verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback);
+
+        // Send IPv4 packet and verify no more sending.
+        mSocketClient.sendMulticastPacket(ipv4Packet);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket, times(3)).send(ipv4Packet);
+        verify(socket2).send(ipv4Packet);
+    }
+
+    @Test
+    public void testNotifyNetworkUnrequested_SocketsOnNullNetwork() {
+        final MdnsInterfaceSocket otherSocket = mock(MdnsInterfaceSocket.class);
+        final SocketCallback callback = expectSocketCallback(
+                mListener, null /* requestedNetwork */);
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        doReturn(createEmptyNetworkInterface()).when(otherSocket).getInterface();
+
+        callback.onSocketCreated(null /* network */, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(null);
+        callback.onSocketCreated(null /* network */, otherSocket, List.of());
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+        verify(mSocketCreationCallback, never()).onAllSocketsDestroyed(null /* network */);
+        mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(mListener));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+
+        verify(mProvider).unrequestSocket(callback);
+        verify(mSocketCreationCallback).onAllSocketsDestroyed(null /* network */);
+    }
+
+    @Test
+    public void testSocketCreatedAndDestroyed_NullNetwork() throws IOException {
+        final MdnsInterfaceSocket otherSocket = mock(MdnsInterfaceSocket.class);
+        final SocketCallback callback = expectSocketCallback(mListener, null /* network */);
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        doReturn(createEmptyNetworkInterface()).when(otherSocket).getInterface();
+
+        callback.onSocketCreated(null /* network */, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(null);
+        callback.onSocketCreated(null /* network */, otherSocket, List.of());
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+        // Notify socket destroyed
+        callback.onInterfaceDestroyed(null /* network */, mSocket);
+        verifyNoMoreInteractions(mSocketCreationCallback);
+        callback.onInterfaceDestroyed(null /* network */, otherSocket);
+        verify(mSocketCreationCallback).onAllSocketsDestroyed(null /* network */);
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index bd59156..4e69f47 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -90,6 +90,7 @@
 
     private static final long TEST_TTL = 120000L;
     private static final long TEST_ELAPSED_REALTIME = 123L;
+    private static final long TEST_TIMEOUT_MS = 10_000L;
 
     @Mock
     private MdnsServiceBrowserListener mockListenerOne;
@@ -1169,7 +1170,7 @@
     }
 
     @Test
-    public void testNotifyAllServicesRemoved() {
+    public void testNotifySocketDestroyed() throws Exception {
         client = new MdnsServiceTypeClient(
                 SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
 
@@ -1182,8 +1183,18 @@
                 .setResolveInstanceName("Instance1").build();
 
         client.startSendAndReceive(mockListenerOne, resolveOptions);
+        // Ensure the first task is executed so it schedules a future task
+        currentThreadExecutor.getAndClearSubmittedFuture().get(
+                TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
 
+        // Filing the second request cancels the first future
+        verify(expectedSendFutures[0]).cancel(true);
+
+        // Ensure it gets executed too
+        currentThreadExecutor.getAndClearSubmittedFuture().get(
+                TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
         // Complete response from instanceName
         client.processResponse(createResponse(
                         requestedInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
@@ -1196,7 +1207,9 @@
                         Collections.emptyMap() /* textAttributes */, TEST_TTL),
                 INTERFACE_INDEX, mockNetwork);
 
-        client.notifyAllServicesRemoved();
+        verify(expectedSendFutures[1], never()).cancel(true);
+        client.notifySocketDestroyed();
+        verify(expectedSendFutures[1]).cancel(true);
 
         // mockListenerOne gets notified for the requested instance
         final InOrder inOrder1 = inOrder(mockListenerOne);
@@ -1261,6 +1274,7 @@
         private long lastScheduledDelayInMs;
         private Runnable lastScheduledRunnable;
         private Runnable lastSubmittedRunnable;
+        private Future<?> lastSubmittedFuture;
         private int futureIndex;
 
         FakeExecutor() {
@@ -1272,6 +1286,7 @@
         public Future<?> submit(Runnable command) {
             Future<?> future = super.submit(command);
             lastSubmittedRunnable = command;
+            lastSubmittedFuture = future;
             return future;
         }
 
@@ -1303,6 +1318,12 @@
             lastSubmittedRunnable = null;
             return val;
         }
+
+        Future<?> getAndClearSubmittedFuture() {
+            Future<?> val = lastSubmittedFuture;
+            lastSubmittedFuture = null;
+            return val;
+        }
     }
 
     private MdnsPacket createResponse(
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 744ec84..4b87556 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -349,8 +349,8 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        // Expect the socket destroy for tethered interface.
-        testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
+        // There was still a tethered interface, but no callback should be sent once unregistered
+        testCallback3.expectedNoCallback();
     }
 
     private RtNetlinkAddressMessage createNetworkAddressUpdateNetLink(
@@ -528,7 +528,8 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
-        testCallback.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
+        // No callback sent when unregistered
+        testCallback.expectedNoCallback();
         verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
         verify(mTm, times(1)).unregisterTetheringEventCallback(any());