Merge changes I99ce72bd,I5a11a802

* changes:
  Add retries for WifiManager#connect
  Use WifiManager.connect to reconnect wifi
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index bce9880..103906a 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -28,7 +28,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -56,6 +55,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.system.Os;
 import android.system.OsConstants;
 import android.text.TextUtils;
@@ -73,11 +73,13 @@
 import java.net.Socket;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
 
 public final class CtsNetUtils {
     private static final String TAG = CtsNetUtils.class.getSimpleName();
@@ -86,6 +88,13 @@
 
     private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 10_000;
     private static final int CONNECTIVITY_CHANGE_TIMEOUT_SECS = 30;
+    private static final int MAX_WIFI_CONNECT_RETRIES = 10;
+    private static final int WIFI_CONNECT_INTERVAL_MS = 500;
+
+    // Constants used by WifiManager.ActionListener#onFailure. Although onFailure is SystemApi,
+    // the error code constants are not (they probably should be ?)
+    private static final int WIFI_ERROR_IN_PROGRESS = 1;
+    private static final int WIFI_ERROR_BUSY = 2;
     private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
     private static final String PRIVATE_DNS_MODE_STRICT = "hostname";
     public static final int HTTP_PORT = 80;
@@ -211,32 +220,21 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiver(receiver, filter);
 
-        boolean connected = false;
-        final String err = "Wifi must be configured to connect to an access point for this test";
         try {
-            clearWifiBlacklist();
+            // Clear the wifi config blocklist (not the BSSID blocklist)
+            clearWifiBlocklist();
             SystemUtil.runShellCommand("svc wifi enable");
-            final WifiConfiguration config = maybeAddVirtualWifiConfiguration();
-            if (config == null) {
-                // TODO: this may not clear the BSSID blacklist, as opposed to
-                // mWifiManager.connect(config)
-                assertTrue("Error reconnecting wifi", runAsShell(NETWORK_SETTINGS,
-                        mWifiManager::reconnect));
-            } else {
-                // When running CTS, devices are expected to have wifi networks pre-configured.
-                // This condition is only hit on virtual devices.
-                final Integer error = runAsShell(NETWORK_SETTINGS, () -> {
-                    final ConnectWifiListener listener = new ConnectWifiListener();
-                    mWifiManager.connect(config, listener);
-                    return listener.connectFuture.get(
-                            CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
-                });
-                assertNull("Error connecting to wifi: " + error, error);
-            }
+            final WifiConfiguration config = getOrCreateWifiConfiguration();
+            connectToWifiConfig(config);
+
             // Ensure we get an onAvailable callback and possibly a CONNECTIVITY_ACTION.
             wifiNetwork = callback.waitForAvailable();
-            assertNotNull(err + ": onAvailable callback not received", wifiNetwork);
-            connected = !expectLegacyBroadcast || receiver.waitForState();
+            assertNotNull("onAvailable callback not received after connecting to " + config.SSID,
+                    wifiNetwork);
+            if (expectLegacyBroadcast) {
+                assertTrue("CONNECTIVITY_ACTION not received after connecting to " + config.SSID,
+                        receiver.waitForState());
+            }
         } catch (InterruptedException ex) {
             fail("connectToWifi was interrupted");
         } finally {
@@ -244,10 +242,30 @@
             mContext.unregisterReceiver(receiver);
         }
 
-        assertTrue(err + ": CONNECTIVITY_ACTION not received", connected);
         return wifiNetwork;
     }
 
+    private void connectToWifiConfig(WifiConfiguration config) {
+        for (int i = 0; i < MAX_WIFI_CONNECT_RETRIES; i++) {
+            final Integer error = runAsShell(NETWORK_SETTINGS, () -> {
+                final ConnectWifiListener listener = new ConnectWifiListener();
+                mWifiManager.connect(config, listener);
+                return listener.connectFuture.get(
+                        CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
+            });
+
+            if (error == null) return;
+
+            // Only retry for IN_PROGRESS and BUSY
+            if (error != WIFI_ERROR_IN_PROGRESS && error != WIFI_ERROR_BUSY) {
+                fail("Failed to connect to " + config.SSID + ": " + error);
+            }
+
+            Log.w(TAG, "connect failed with " + error + "; waiting before retry");
+            SystemClock.sleep(WIFI_CONNECT_INTERVAL_MS);
+        }
+    }
+
     private static class ConnectWifiListener implements WifiManager.ActionListener {
         /**
          * Future completed when the connect process ends. Provides the error code or null if none.
@@ -264,7 +282,7 @@
         }
     }
 
-    private WifiConfiguration maybeAddVirtualWifiConfiguration() {
+    private WifiConfiguration getOrCreateWifiConfiguration() {
         final List<WifiConfiguration> configs = runAsShell(NETWORK_SETTINGS,
                 mWifiManager::getConfiguredNetworks);
         // If no network is configured, add a config for virtual access points if applicable
@@ -275,8 +293,24 @@
 
             return virtualConfig;
         }
-        // No need to add a configuration: there is already one
-        return null;
+        // No need to add a configuration: there is already one.
+        if (configs.size() > 1) {
+            // For convenience in case of local testing on devices with multiple saved configs,
+            // prefer the first configuration that is in range.
+            // In actual tests, there should only be one configuration, and it should be usable as
+            // assumed by WifiManagerTest.testConnect.
+            Log.w(TAG, "Multiple wifi configurations found: "
+                    + configs.stream().map(c -> c.SSID).collect(Collectors.joining(", ")));
+            final List<ScanResult> scanResultsList = getWifiScanResults();
+            Log.i(TAG, "Scan results: " + scanResultsList.stream().map(c ->
+                    c.SSID + " (" + c.level + ")").collect(Collectors.joining(", ")));
+            final Set<String> scanResults = scanResultsList.stream().map(
+                    s -> "\"" + s.SSID + "\"").collect(Collectors.toSet());
+
+            return configs.stream().filter(c -> scanResults.contains(c.SSID))
+                    .findFirst().orElse(configs.get(0));
+        }
+        return configs.get(0);
     }
 
     private List<ScanResult> getWifiScanResults() {
@@ -327,11 +361,11 @@
     }
 
     /**
-     * Re-enable wifi networks that were blacklisted, typically because no internet connection was
+     * Re-enable wifi networks that were blocklisted, typically because no internet connection was
      * detected the last time they were connected. This is necessary to make sure wifi can reconnect
      * to them.
      */
-    private void clearWifiBlacklist() {
+    private void clearWifiBlocklist() {
         runAsShell(NETWORK_SETTINGS, ACCESS_WIFI_STATE, () -> {
             for (WifiConfiguration cfg : mWifiManager.getConfiguredNetworks()) {
                 assertTrue(mWifiManager.enableNetwork(cfg.networkId, false /* attemptConnect */));