Merge "Release ClientNetworkRequest before marking request unfulfillable" into main
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 0352ad5..149979f 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -597,10 +597,12 @@
             final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
             if (cri == null) return;
 
+            // Release ClientNetworkRequest before sending onUnavailable() to ensure the app
+            // first receives an onLost() callback if a network had been created.
+            releaseClientNetworkRequest(cri);
             for (NetworkRequest request : cri.requests) {
                 mProvider.declareNetworkRequestUnfulfillable(request);
             }
-            releaseClientNetworkRequest(cri);
         }
     }
 
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
index babcba9..ee5b4ee 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
@@ -38,12 +38,14 @@
 import android.net.NetworkSpecifier
 import android.net.RouteInfo
 import android.os.Build
+import android.os.Handler
 import android.os.HandlerThread
 import android.os.ParcelFileDescriptor
 import com.android.server.net.L2capNetwork.L2capIpClient
 import com.android.server.net.L2capPacketForwarder
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
 import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
 import com.android.testutils.TestableNetworkCallback
@@ -59,6 +61,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mockito.doAnswer
@@ -394,4 +397,34 @@
         val cb2 = requestNetwork(nr)
         cb2.expectAvailableCallbacks(anyNetwork(), validated = false)
     }
+
+    /** Test to ensure onLost() is sent before onUnavailable() when the network is torn down. */
+    @Test
+    fun testClientNetwork_gracefulTearDown() {
+        val specifier = L2capNetworkSpecifier.Builder()
+            .setRole(ROLE_CLIENT)
+            .setHeaderCompression(HEADER_COMPRESSION_NONE)
+            .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+            .setPsm(PSM)
+            .build()
+
+        val nr = REQUEST.copyWithSpecifier(specifier)
+        val cb = requestNetwork(nr)
+        cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+
+        // Capture the L2capPacketForwarder callback object to tear down the network.
+        val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
+        val forwarderCbCaptor = ArgumentCaptor.forClass(L2capPacketForwarder.ICallback::class.java)
+        verify(providerDeps).createL2capPacketForwarder(
+                handlerCaptor.capture(), any(), any(), any(), forwarderCbCaptor.capture())
+        val handler = handlerCaptor.value
+        val forwarderCb = forwarderCbCaptor.value
+
+        // Trigger a forwarding error
+        handler.post { forwarderCb.onError() }
+        handler.waitForIdle(HANDLER_TIMEOUT_MS)
+
+        cb.expect<Lost>()
+        cb.expect<Unavailable>()
+    }
 }