[Tests]Adding tests for connection with network id

Includes new tests for connecting to a network with network id in
various scenarios.

wifi_test_utils is updated for making appropriate calls to
the newly added facade for connection with network id.

Bug: 34774763
Test: Tested and verified all tests locally.
Change-Id: I19de30e7d45ddfb81c509342350087781ac5d8d8
diff --git a/acts/framework/acts/test_utils/wifi/wifi_constants.py b/acts/framework/acts/test_utils/wifi/wifi_constants.py
index 1810130..f51c336 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_constants.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_constants.py
@@ -21,3 +21,4 @@
 
 # These constants will be used by the ACTS wifi tests.
 CONNECT_BY_CONFIG_SUCCESS = 'WifiManagerConnectByConfigOnSuccess'
+CONNECT_BY_NETID_SUCCESS = 'WifiManagerConnectByNetIdOnSuccess'
diff --git a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
index 382f172..ac9b5e7 100755
--- a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
@@ -49,6 +49,7 @@
 class WifiEnums():
 
     SSID_KEY = "SSID"
+    NETID_KEY = "network_id"
     BSSID_KEY = "BSSID"
     PWD_KEY = "password"
     frequency_key = "frequency"
@@ -571,6 +572,40 @@
         "Failed to remove these configured Wi-Fi networks: %s" % networks)
 
 
+    def toggle_airplane_mode_on_and_off(self):
+        """Turn ON and OFF Airplane mode.
+
+        Args: None.
+        Returns: Assert if turning on/off Airplane mode fails.
+
+        """
+        self.log.debug("Toggling Airplane mode ON.")
+        asserts.assert_true(
+            force_airplane_mode(self.dut, True),
+            "Can not turn on airplane mode on: %s" % self.dut.serial)
+        time.sleep(DEFAULT_TIMEOUT)
+        self.log.debug("Toggling Airplane mode OFF.")
+        asserts.assert_true(
+            force_airplane_mode(self.dut, False),
+            "Can not turn on airplane mode on: %s" % self.dut.serial)
+        time.sleep(DEFAULT_TIMEOUT)
+
+
+    def toggle_wifi_off_and_on(self):
+        """Turn OFF and ON WiFi.
+
+        Args: None.
+        Returns: Assert if turning off/on WiFi fails.
+
+        """
+        self.log.debug("Toggling wifi OFF.")
+        wutils.wifi_toggle_state(self.dut, False)
+        time.sleep(DEFAULT_TIMEOUT)
+        self.log.debug("Toggling wifi ON.")
+        wutils.wifi_toggle_state(self.dut, True)
+        time.sleep(DEFAULT_TIMEOUT)
+
+
 def wifi_forget_network(ad, net_ssid):
     """Remove configured Wifi network on an android device.
 
@@ -807,6 +842,43 @@
         ad.droid.wifiStopTrackingStateChange()
 
 
+def wait_for_connect(ad, tries):
+    """Wait for a connect event on queue and pop when available.
+
+    Args:
+        ad: An Android device object.
+        tries: An integer that is the number of times to try before failing.
+
+    Returns:
+        A dict with details of the connection data, which looks like this:
+        {
+         'time': 1485460337798,
+         'name': 'WifiNetworkConnected',
+         'data': {
+                  'rssi': -27,
+                  'is_24ghz': True,
+                  'mac_address': '02:00:00:00:00:00',
+                  'network_id': 1,
+                  'BSSID': '30:b5:c2:33:d3:fc',
+                  'ip_address': 117483712,
+                  'link_speed': 54,
+                  'supplicant_state': 'completed',
+                  'hidden_ssid': False,
+                  'SSID': 'wh_ap1_2g',
+                  'is_5ghz': False}
+        }
+
+    """
+    connect_result = None
+    for i in range(tries):
+        try:
+            connect_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
+            break
+        except Empty:
+            pass
+    return connect_result
+
+
 def wifi_connect(ad, network, num_of_tries=1, assert_on_fail=True):
     """Connect an Android device to a wifi network.
 
@@ -825,8 +897,8 @@
                         failure signals.
 
     Returns:
-        If assert_on_fail is False, function returns True if the toggle was
-        successful, False otherwise. If assert_on_fail is True, no return value.
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
     """
     return _assert_on_fail_handler(
         _wifi_connect, assert_on_fail, ad, network, num_of_tries=num_of_tries)
@@ -856,14 +928,7 @@
     ad.log.info("Starting connection process to %s", expected_ssid)
     try:
         event = ad.ed.pop_event(wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 30)
-        connect_result = None
-        for i in range(num_of_tries):
-            try:
-                connect_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED,
-                                                 30)
-                break
-            except Empty:
-                pass
+        connect_result = wait_for_connect(ad, num_of_tries)
         asserts.assert_true(connect_result,
                             "Failed to connect to Wi-Fi network %s on %s" %
                             (network, ad.serial))
@@ -893,6 +958,81 @@
         ad.droid.wifiStopTrackingStateChange()
 
 
+def wifi_connect_by_id(ad, network_id, num_of_tries=1, assert_on_fail=True):
+    """Connect an Android device to a wifi network using network Id.
+
+    Start connection to the wifi network, with the given network Id, wait for
+    the "connected" event, then verify the connected network is the one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network_id: Integer specifying the network id of the network.
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
+    """
+    _assert_on_fail_handler(_wifi_connect_by_id, assert_on_fail, ad,
+                            network_id, num_of_tries)
+
+
+def _wifi_connect_by_id(ad, network_id, num_of_tries=1):
+    """Connect an Android device to a wifi network using it's network id.
+
+    Start connection to the wifi network, with the given network id, wait for
+    the "connected" event, then verify the connected network is the one requested.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network_id: Integer specifying the network id of the network.
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+    """
+    # Clear all previous connect events.
+    ad.ed.clear_events(wifi_constants.WIFI_CONNECTED)
+    ad.droid.wifiStartTrackingStateChange()
+    ad.droid.wifiConnectByNetworkId(network_id)
+    ad.log.info("Starting connection to network with id %d", network_id)
+    try:
+        event = ad.ed.pop_event(wifi_constants.CONNECT_BY_NETID_SUCCESS, 60)
+        connect_result = wait_for_connect(ad, num_of_tries)
+        asserts.assert_true(connect_result,
+                            "Failed to connect to Wi-Fi network using network id")
+        ad.log.debug("Wi-Fi connection result: %s", connect_result)
+        actual_id = connect_result['data'][WifiEnums.NETID_KEY]
+        asserts.assert_equal(actual_id, network_id,
+                             "Connected to the wrong network on %s."
+                             "Expected network id = %d, but got %d." %
+                             (ad.serial, network_id, actual_id))
+        expected_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        ad.log.info("Connected to Wi-Fi network %s with %d network id.",
+                     expected_ssid, network_id)
+
+        # Wait for data connection to stabilize.
+        time.sleep(5)
+
+        internet = validate_connection(ad, DEFAULT_PING_ADDR)
+        if not internet:
+            raise signals.TestFailure("Failed to connect to internet on %s" %
+                                      expected_ssid)
+    except Empty:
+        asserts.fail("Failed to connect to network with id %d on %s" %
+                    (network_id, ad.serial))
+    except Exception as error:
+        ad.log.error("Failed to connect to network with id %d with error %s",
+                      network_id, error)
+        raise signals.TestFailure("Failed to connect to network with network"
+                                  " id %d" % network_id)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
 def start_wifi_single_scan(ad, scan_setting):
     """Starts wifi single shot scan.
 
diff --git a/acts/tests/google/wifi/WifiManagerTest.py b/acts/tests/google/wifi/WifiManagerTest.py
index 8532d24..55ef8cf 100755
--- a/acts/tests/google/wifi/WifiManagerTest.py
+++ b/acts/tests/google/wifi/WifiManagerTest.py
@@ -27,7 +27,11 @@
 from acts import asserts
 
 WifiEnums = wutils.WifiEnums
-
+# Default timeout used for reboot, toggle WiFi and Airplane mode,
+# for the system to settle down after the operation.
+DEFAULT_TIMEOUT = 10
+BAND_2GHZ = 0
+BAND_5GHZ = 1
 
 class WifiManagerTest(acts.base_test.BaseTestClass):
     """Tests for APIs in Android's WifiManager class.
@@ -49,7 +53,7 @@
         self.user_params["additional_energy_info_models"] = []
         self.user_params["additional_tdls_models"] = []
         req_params = ("iot_networks", "open_network", "config_store_networks",
-                      "iperf_server_address")
+                      "iperf_server_address", "reference_networks")
         opt_param = ("additional_energy_info_models", "additional_tdls_models")
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
@@ -65,12 +69,17 @@
         self.user_params["tdls_models"] = list(
             set(self.user_params["tdls_models"]))
         asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+        asserts.assert_true(
             len(self.iot_networks) > 0,
             "Need at least one iot network with psk.")
         wutils.wifi_toggle_state(self.dut, True)
         self.iot_networks = self.iot_networks + [self.open_network]
         self.iperf_server = self.iperf_servers[0]
         self.iot_test_prefix = "test_connection_to-"
+        self.wpapsk_2g = self.reference_networks[0]["2g"]
+        self.wpapsk_5g = self.reference_networks[0]["5g"]
 
     def setup_test(self):
         self.dut.droid.wakeLockAcquireBright()
@@ -106,6 +115,62 @@
         wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
         wutils.wifi_connect(ad, network, num_of_tries=3)
 
+    def get_connection_data(self, dut, network):
+        """Get network id and ssid info from connection data.
+
+        Args:
+            dut: The Android device object under test.
+            network: dict representing the network to connect to.
+
+        Returns:
+            A convenience dict with the connected network's ID and SSID.
+
+        """
+        params = (network, dut)
+        self.connect_to_wifi_network(params)
+        connect_data = dut.droid.wifiGetConnectionInfo()
+        ssid_id_dict = dict()
+        ssid_id_dict[WifiEnums.NETID_KEY] = connect_data[WifiEnums.NETID_KEY]
+        ssid_id_dict[WifiEnums.SSID_KEY] = connect_data[WifiEnums.SSID_KEY]
+        return ssid_id_dict
+
+    def connect_multiple_networks(self, dut):
+        """Connect to one 2.4GHz and one 5Ghz network.
+
+        Args:
+            dut: The Android device object under test.
+
+        Returns:
+            A list with the connection details for the 2GHz and 5GHz networks.
+
+        """
+        network_list = list()
+        connect_2g_data = self.get_connection_data(dut, self.wpapsk_2g)
+        network_list.append(connect_2g_data)
+        connect_5g_data = self.get_connection_data(dut, self.wpapsk_5g)
+        network_list.append(connect_5g_data)
+        return network_list
+
+    def connect_to_wifi_network_with_id(self, network_id, network_ssid):
+        """Connect to the given network using network id and verify SSID.
+
+        Args:
+            network_id: int Network Id of the network.
+            network_ssid: string SSID of the network.
+
+        Returns: True if connect using network id was successful;
+                 False otherwise.
+
+        """
+        wutils.wifi_connect_by_id(self.dut, network_id)
+        connect_data = self.dut.droid.wifiGetConnectionInfo()
+        connect_ssid = reconnect_data[WifiEnums.SSID_KEY]
+        self.log.debug("Expected SSID = %s Connected SSID = %s" %(network_ssid,
+                        connect_ssid))
+        if connect_ssid != network_ssid:
+            return False
+        return True
+
     def run_iperf_client(self, params):
         """Run iperf traffic after connection.
 
@@ -234,6 +299,100 @@
                 nw[WifiEnums.BSSID_KEY] != ssid,
                 "Found forgotten network %s in configured networks." % ssid)
 
+    def test_reconnect_to_connected_network(self):
+        """Connect to a network and immediately issue reconnect.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Reconnect to the network using its network id.
+        3. Connect to a 5GHz network.
+        4. Reconnect to the network using its network id.
+
+        """
+        connect_2g_data = self.get_connection_data(self.dut, self.wpapsk_2g)
+        reconnect_2g = self.connect_to_wifi_network_with_id(
+                               connect_2g_data[WifiEnums.NETID_KEY],
+                               connect_2g_data[WifiEnums.SSID_KEY])
+        if not reconnect_2g:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                       " 2GHz network.")
+        connect_5g_data = self.get_connection_data(self.dut, self.wpapsk_5g)
+        reconnect_5g = self.connect_to_wifi_network_with_id(
+                               connect_5g_data[WifiEnums.NETID_KEY],
+                               connect_5g_data[WifiEnums.SSID_KEY])
+        if not reconnect_5g:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                       " 5GHz network.")
+
+    def test_reconnect_to_previously_connected(self):
+        """Connect to multiple networks and reconnect to the previous network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Reconnect to the 2GHz network using its network id.
+        4. Reconnect to the 5GHz network using its network id.
+
+        """
+        connect_2g_data = self.get_connection_data(self.dut, self.wpapsk_2g)
+        connect_5g_data = self.get_connection_data(self.dut, self.wpapsk_5g)
+        reconnect_2g = self.connect_to_wifi_network_with_id(
+                               connect_2g_data[WifiEnums.NETID_KEY],
+                               connect_2g_data[WifiEnums.SSID_KEY])
+        if not reconnect_2g:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                      " 2GHz network.")
+        reconnect_5g = self.connect_to_wifi_network_with_id(
+                               connect_5g_data[WifiEnums.NETID_KEY],
+                               connect_5g_data[WifiEnums.SSID_KEY])
+        if not reconnect_5g:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                      " 5GHz network.")
+
+    def test_reconnect_toggle_wifi(self):
+        """Connect to multiple networks, turn off/on wifi, then reconnect to
+           a previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Turn WiFi OFF/ON.
+        4. Reconnect to the non-current network.
+
+        """
+        connect_2g_data = self.get_connection_data(self.dut, self.wpapsk_2g)
+        connect_5g_data = self.get_connection_data(self.dut, self.wpapsk_5g)
+        wutils.toggle_wifi_off_and_on()
+        reconnect_to = self.get_enabled_network(connect_2g_data, connect_5g_data)
+        reconnect = self.connect_to_wifi_network_with_id(
+                            reconnect_to[WifiEnums.NETID_KEY],
+                            reconnect_to[WifiEnums.SSID_KEY])
+        if not reconnect:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                      " network after toggling WiFi.")
+
+    def test_reconnect_toggle_airplane(self):
+        """Connect to multiple networks, turn on/off Airplane moce, then
+           reconnect a previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Turn ON/OFF Airplane mode.
+        4. Reconnect to the non-current network.
+
+        """
+        connect_2g_data = self.get_connection_data(self.dut, self.wpapsk_2g)
+        connect_5g_data = self.get_connection_data(self.dut, self.wpapsk_5g)
+        wutils.toggle_airplane_mode_on_and_off()
+        reconnect_to = self.get_enabled_network(connect_2g_data, connect_5g_data)
+        reconnect = self.connect_to_wifi_network_with_id(
+                            reconnect_to[WifiEnums.NETID_KEY],
+                            reconnect_to[WifiEnums.SSID_KEY])
+        if not reconnect:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                      " network after toggling Airplane mode.")
+
     @acts.signals.generated_test
     def test_iot_with_password(self):
         params = list(