[CS] Remove timeout event after first available

Guarantees that timeouts are only delivered if a network never
becomes available. Once a network is available the timeout is
canceled.

Bug: 31402633
Test: all timeout related unit tests pass (new one added)

(cherry picked from commit 681fcda5896fd6c94f8a543ef9983c0a0644e87e)

Change-Id: I7cd3086544c881915fc6dbf14b87a24ab0cd8748
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index eae4bc7..8c38ed6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5319,6 +5319,7 @@
     // notify only this one new request of the current state
     protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
         int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+        mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
         if (nri.mPendingIntent == null) {
             callCallbackForRequest(nri, nai, notifyType, 0);
         } else {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f95a8c6..a06577e 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -727,10 +727,7 @@
     static private void waitFor(Criteria criteria) {
         int delays = 0;
         while (!criteria.get()) {
-            try {
-                Thread.sleep(50);
-            } catch (InterruptedException e) {
-            }
+            sleepFor(50);
             if (++delays == 10) fail();
         }
     }
@@ -2327,10 +2324,30 @@
         networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        try {
-            Thread.sleep(15);
-        } catch (InterruptedException e) {
-        }
+        sleepFor(15);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that a satisfied network request followed by a disconnected (lost) network does
+     * not trigger onUnavailable() once the time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 500);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        sleepFor(20);
+        mWiFiNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        sleepFor(600);
         networkCallback.assertNoCallback();
     }
 
@@ -2372,10 +2389,7 @@
         // pass timeout and validate that no callbacks
         // Note: doesn't validate that nothing called from CS since even if called the CM already
         // unregisters the callback and won't pass it through!
-        try {
-            Thread.sleep(15);
-        } catch (InterruptedException e) {
-        }
+        sleepFor(15);
         networkCallback.assertNoCallback();
 
         // create a network satisfying request - validate that request not triggered
@@ -2788,4 +2802,13 @@
             mCm.unregisterNetworkCallback(pendingIntent);
         }
     }
+
+    /* test utilities */
+    static private void sleepFor(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+
+    }
 }