Merge "Add serial urls probe logic for NetworkMonitor" into main
diff --git a/res/values/config.xml b/res/values/config.xml
index 8d6b64e..d9ee36d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -51,6 +51,8 @@
     </string-array>
     <string-array name="config_captive_portal_https_urls" translatable="false">
     </string-array>
+    <bool name="config_probe_multi_http_https_url_serial">false</bool>
+    <integer name="config_serial_url_probe_gap_time">1000</integer>
 
     <!-- Customized default DNS Servers address. -->
     <string-array name="config_default_dns_servers" translatable="false">
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index 0aeaaec..8e8fe08 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -42,6 +42,8 @@
             <item type="array" name="config_captive_portal_http_urls"/>
             <item type="array" name="config_captive_portal_https_urls"/>
             <item type="array" name="config_captive_portal_fallback_urls"/>
+            <item type="bool" name="config_probe_multi_http_https_url_serial"/>
+            <item type="integer" name="config_serial_url_probe_gap_time"/>
             <item type="bool" name="config_no_sim_card_uses_neighbor_mcc"/>
             <!-- Configuration value for DhcpResults -->
             <item type="array" name="config_default_dns_servers"/>
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index eaed8e5..3ae8557 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -517,6 +517,8 @@
     private final boolean mIsCaptivePortalCheckEnabled;
 
     private boolean mUseHttps;
+    private final boolean mUseSerialProbe;
+    private final int mSerialProbeGapTime;
     /**
      * The total number of completed validation attempts (network validated or a captive portal was
      * detected) for this NetworkMonitor instance.
@@ -679,6 +681,8 @@
                 && deps.isFeatureSupported(mContext, FEATURE_DDR_IN_CONNECTIVITY)
                 && deps.isFeatureSupported(mContext, FEATURE_DDR_IN_DNSRESOLVER);
         mUseHttps = getUseHttpsValidation();
+        mUseSerialProbe = getUseSerialProbeValidation();
+        mSerialProbeGapTime = getSerialProbeGapTime();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
         mCaptivePortalFallbackSpecs =
                 makeCaptivePortalFallbackProbeSpecs(getCustomizedContextOrDefault());
@@ -2392,6 +2396,16 @@
                         R.bool.config_force_dns_probe_private_ip_no_internet);
     }
 
+    private boolean getUseSerialProbeValidation() {
+        return mContext.getResources().getBoolean(
+                R.bool.config_probe_multi_http_https_url_serial);
+    }
+
+    private int getSerialProbeGapTime() {
+        return mContext.getResources().getInteger(
+                R.integer.config_serial_url_probe_gap_time);
+    }
+
     private boolean getUseHttpsValidation() {
         return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
                 CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
@@ -3426,14 +3440,26 @@
             // Probe capport API with the first HTTP probe.
             // TODO: Have the capport probe as a different probe for cleanliness.
             final URL urlMaybeWithCapport = httpUrls[0];
+            int delayCount=0;
             for (final URL url : httpUrls) {
-                futures.add(ecs.submit(() -> new HttpProbe(properties, proxy, url,
-                        url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe()));
+                final int cnt = delayCount++;
+                futures.add(ecs.submit(() -> {
+                    if (mUseSerialProbe && cnt > 0) {
+                        mDependencies.sleep(mSerialProbeGapTime * cnt);
+                    }
+                    return new HttpProbe(properties, proxy, url,
+                            url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe();
+                }));
             }
-
+            delayCount=0;
             for (final URL url : httpsUrls) {
-                futures.add(ecs.submit(() -> new HttpsProbe(properties, proxy, url, capportApiUrl)
-                        .sendProbe()));
+                final int cnt = delayCount++;
+                futures.add(ecs.submit(() -> {
+                    if (mUseSerialProbe && cnt > 0) {
+                        mDependencies.sleep(mSerialProbeGapTime * cnt);
+                    }
+                    return new HttpsProbe(properties, proxy, url, capportApiUrl).sendProbe();
+                }));
             }
 
             final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>();
@@ -3778,6 +3804,13 @@
         public void onExecutorServiceCreated(@NonNull ExecutorService ecs) {
         }
 
+        /**
+         * Wait for another round of serial probe
+         */
+        public void sleep(int time) throws InterruptedException {
+            Thread.sleep((long)time);
+        }
+
         public static final Dependencies DEFAULT = new Dependencies();
     }
 
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 37b157b..e9bd616 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -226,6 +226,7 @@
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
@@ -320,6 +321,7 @@
     private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
 
     private static final int HANDLER_TIMEOUT_MS = 1000;
+    private static final int SERIAL_PROBE_GAP_TIME_MS = 500;
     private static final int TEST_MIN_STALL_EVALUATE_INTERVAL_MS = 500;
     private static final int TEST_MIN_VALID_STALL_DNS_TIME_THRESHOLD_MS = 5000;
     private static final int STALL_EXPECTED_LAST_PROBE_TIME_MS =
@@ -373,6 +375,7 @@
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
 
     private FakeDns mFakeDns;
+    private Semaphore mSerialProbeLock;
 
     @GuardedBy("mThreadsToBeCleared")
     private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>();
@@ -474,6 +477,7 @@
         initHttpConnection(mFallbackConnection);
         initHttpConnection(mOtherFallbackConnection);
 
+        mSerialProbeLock = new Semaphore(0);
         mFakeDns = new FakeDns(mNetwork, mDnsResolver);
         mFakeDns.startMocking();
         // Set private dns suffix answer. sendPrivateDnsProbe() in NetworkMonitor send probe with
@@ -3417,6 +3421,36 @@
     }
 
     @Test
+    public void testSerialProbesOnFirstValidNetwork() throws Exception {
+        setupResourceForSerialProbes();
+        setStatus(mOtherHttpsConnection1, 204);
+        runSerialProbesNetworkTest(NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+        verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(2)).sleep(anyInt());
+        verify(mCleartextDnsNetwork, timeout(HANDLER_TIMEOUT_MS).times(2)).openConnection(any());
+    }
+
+    @Test
+    public void testSerialProbesOnSecondValidNetwork() throws Exception {
+        setupResourceForSerialProbes();
+        setStatus(mOtherHttpsConnection2, 204);
+        mSerialProbeLock.release(2);
+        runSerialProbesNetworkTest(NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+        verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(2)).sleep(anyInt());
+        verify(mCleartextDnsNetwork, timeout(HANDLER_TIMEOUT_MS).times(4)).openConnection(any());
+    }
+
+    @Test
+    public void testSerialProbesOnInValidNetwork() throws Exception {
+        setupResourceForSerialProbes();
+        mSerialProbeLock.release(2);
+        runSerialProbesNetworkTest(VALIDATION_RESULT_INVALID, 0);
+        verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(2)).sleep(anyInt());
+        verify(mCleartextDnsNetwork, timeout(HANDLER_TIMEOUT_MS).times(4)).openConnection(any());
+    }
+
+    @Test
     public void testIsCaptivePortal_FromExternalSource() throws Exception {
         assumeTrue(CaptivePortalDataShimImpl.isSupported());
         assumeTrue(ShimUtils.isAtLeastS());
@@ -3523,6 +3557,18 @@
         verify(mCallbacks, never()).showProvisioningNotification(any(), any());
     }
 
+    private void waitForSerialProbes(int time) throws InterruptedException {
+        mSerialProbeLock.tryAcquire(time, TimeUnit.MILLISECONDS);
+    }
+
+    private void setupResourceForSerialProbes() {
+        doReturn(true).when(mResources)
+                .getBoolean(R.bool.config_probe_multi_http_https_url_serial);
+        doReturn(SERIAL_PROBE_GAP_TIME_MS).when(mResources)
+                .getInteger(R.integer.config_serial_url_probe_gap_time);
+        setupResourceForMultipleProbes();
+    }
+
     private void setupResourceForMultipleProbes() {
         // Configure the resource to send multiple probe.
         doReturn(TEST_HTTPS_URLS).when(mResources)
@@ -3620,6 +3666,17 @@
         return nm;
     }
 
+    private void runSerialProbesNetworkTest(int testResult, int probesSucceeded) throws Exception {
+        final WrappedNetworkMonitor monitor = makeMonitor(CELL_METERED_CAPABILITIES);
+        notifyNetworkConnected(monitor, TEST_AGENT_CONFIG,
+                TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES);
+        doAnswer(invocation -> {
+            waitForSerialProbes(invocation.getArgument(0));
+            return null;
+        }).when(mDependencies).sleep(anyInt());
+        verifyNetworkTested(testResult, probesSucceeded, 1);
+    }
+
     private NetworkMonitor runPartialConnectivityNetworkTest(int probesSucceeded)
             throws Exception {
         final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,