Revert "Merge sc-d1-dev 6958804 into master."

This reverts commit 3aed9d58ff5cfdbbafc5f8ec8bedd6a6d1ed2221.

Reason for revert: Certain changes were originally not merged from master to sc-d1-dev. By merging sc-d1-dev back to master, those changes would be lost. In addition, sc-d1-dev has never been used for development for platform/tools/test/connectivity.

Bug: 172861149
Change-Id: Ie2d73b100cebfc098a15021c2ed122af165fd869
diff --git a/acts_tests/tests/google/wifi b/acts_tests/tests/google/wifi
deleted file mode 120000
index 48192ea..0000000
--- a/acts_tests/tests/google/wifi
+++ /dev/null
@@ -1 +0,0 @@
-../../../acts/tests/google/wifi
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/OWNERS b/acts_tests/tests/google/wifi/OWNERS
new file mode 100644
index 0000000..edb3e3e
--- /dev/null
+++ b/acts_tests/tests/google/wifi/OWNERS
@@ -0,0 +1,6 @@
+bkleung@google.com
+dysu@google.com
+etancohen@google.com
+gmoturu@google.com
+rpius@google.com
+satk@google.com
diff --git a/acts_tests/tests/google/wifi/SetupWifiNetworkTest.py b/acts_tests/tests/google/wifi/SetupWifiNetworkTest.py
new file mode 100644
index 0000000..26026ff
--- /dev/null
+++ b/acts_tests/tests/google/wifi/SetupWifiNetworkTest.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import socket
+import sys
+
+from acts import base_test
+from acts.controllers.ap_lib import hostapd_ap_preset
+from acts.controllers.ap_lib import hostapd_bss_settings
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_security
+
+
+class SetupWifiNetworkTest(base_test.BaseTestClass):
+    def wait_for_test_completion(self):
+        port = int(self.user_params["socket_port"])
+        timeout = float(self.user_params["socket_timeout_secs"])
+
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+        server_address = ('localhost', port)
+        logging.info("Starting server socket on localhost port %s", port)
+        sock.bind(('localhost', port))
+        sock.settimeout(timeout)
+        sock.listen(1)
+        logging.info("Waiting for client socket connection")
+        try:
+            connection, client_address = sock.accept()
+        except socket.timeout:
+            logging.error("Did not receive signal. Shutting down AP")
+        except socket.error:
+            logging.error("Socket connection errored out. Shutting down AP")
+        finally:
+            connection.close()
+            sock.shutdown(socket.SHUT_RDWR)
+            sock.close()
+
+    def setup_ap(self):
+        bss_settings = []
+        self.access_point = self.access_points[0]
+        network_type = self.user_params["network_type"]
+        if (network_type == "2G"):
+            self.channel = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        else:
+            self.channel = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+
+        self.ssid = self.user_params["ssid"]
+        self.security = self.user_params["security"]
+        if self.security == "none":
+            self.config = hostapd_ap_preset.create_ap_preset(
+                channel=self.channel,
+                ssid=self.ssid,
+                bss_settings=bss_settings,
+                profile_name='whirlwind')
+        else:
+            self.passphrase = self.user_params["passphrase"]
+            self.hostapd_security = hostapd_security.Security(
+                security_mode=self.security, password=self.passphrase)
+            self.config = hostapd_ap_preset.create_ap_preset(
+                channel=self.channel,
+                ssid=self.ssid,
+                security=self.hostapd_security,
+                bss_settings=bss_settings,
+                profile_name='whirlwind')
+        self.access_point.start_ap(self.config)
+
+    def test_set_up_single_ap(self):
+        req_params = [
+            "AccessPoint", "network_type", "ssid", "passphrase", "security",
+            "socket_port", "socket_timeout_secs"
+        ]
+        opt_params = []
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_params)
+        # Setup the AP environment
+        self.setup_ap()
+        # AP enviroment created. Wait for client to teardown the environment
+        self.wait_for_test_completion()
+
+    def test_set_up_open_ap(self):
+        req_params = [
+            "AccessPoint", "network_type", "ssid", "security", "socket_port",
+            "socket_timeout_secs"
+        ]
+        opt_params = []
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_params)
+        # Setup the AP environment
+        self.setup_ap()
+        # AP enviroment created. Wait for client to teardown the environment
+        self.wait_for_test_completion()
diff --git a/acts_tests/tests/google/wifi/WifiAutoJoinTest.py b/acts_tests/tests/google/wifi/WifiAutoJoinTest.py
new file mode 100755
index 0000000..829fc6c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiAutoJoinTest.py
@@ -0,0 +1,491 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts import base_test
+from acts.test_utils.wifi import wifi_constants
+from acts.test_utils.wifi import wifi_test_utils as wutils
+
+WifiEnums = wutils.WifiEnums
+NETWORK_ID_ERROR = "Network don't have ID"
+NETWORK_ERROR = "Device is not connected to reference network"
+
+
+class WifiAutoJoinTest(base_test.BaseTestClass):
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.tests = ("test_autojoin_out_of_range",
+                      "test_autojoin_Ap1_2g",
+                      "test_autojoin_Ap1_2gto5g",
+                      "test_autojoin_in_AP1_5gto2g",
+                      "test_autojoin_swtich_AP1toAp2",
+                      "test_autojoin_Ap2_2gto5g",
+                      "test_autojoin_Ap2_5gto2g",
+                      "test_autojoin_out_of_range",
+                      "test_autojoin_Ap2_2g",
+                      "test_autojoin_Ap2_2gto5g",
+                      "test_autojoin_in_Ap2_5gto2g",
+                      "test_autojoin_swtich_AP2toAp1",
+                      "test_autojoin_Ap1_2gto5g",
+                      "test_autojoin_Ap1_5gto2g",
+                      "test_autojoin_swtich_to_blacklist_AP",
+                      "test_autojoin_in_blacklist_AP",
+                      "test_autojoin_back_from_blacklist_AP", )
+
+    def setup_class(self):
+        """It will setup the required dependencies from config file and configure
+           the required networks for auto-join testing. Configured networks will
+           not be removed. If networks are already configured it will skip
+           configuring the networks
+
+        Returns:
+            True if successfully configured the requirements for testing.
+        """
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ("reference_networks", "other_network", "atten_val",
+                      "ping_addr", "max_bugreports")
+        self.unpack_userparams(req_params)
+        self.log.debug("Connect networks :: {}".format(self.other_network))
+        configured_networks = self.dut.droid.wifiGetConfiguredNetworks()
+        self.log.debug("Configured networks :: {}".format(configured_networks))
+        count_confnet = 0
+        result = False
+        if self.reference_networks[0]['2g']['ssid'] == self.reference_networks[
+                0]['5g']['ssid']:
+            self.ref_ssid_count = 1
+        else:
+            self.ref_ssid_count = 2  # Different SSID for 2g and 5g
+        for confnet in configured_networks:
+            if confnet[WifiEnums.SSID_KEY] == self.reference_networks[0]['2g'][
+                    'ssid']:
+                count_confnet += 1
+            elif confnet[WifiEnums.SSID_KEY] == self.reference_networks[0][
+                    '5g']['ssid']:
+                count_confnet += 1
+        self.log.info("count_confnet {}".format(count_confnet))
+        if count_confnet == self.ref_ssid_count:
+            return
+        else:
+            self.log.info("Configured networks for testing")
+            self.attenuators[0].set_atten(0)
+            self.attenuators[1].set_atten(90)
+            self.attenuators[2].set_atten(90)
+            wait_time = 15
+            self.dut.droid.wakeLockAcquireBright()
+            self.dut.droid.wakeUpNow()
+            try:
+                self.dut.droid.wifiConnectByConfig(self.reference_networks[0][
+                    '2g'])
+                connect_result = self.dut.ed.pop_event(
+                    wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 1)
+                self.log.info(connect_result)
+                time.sleep(wait_time)
+                if self.ref_ssid_count == 2:  #add 5g network as well
+                    self.dut.droid.wifiConnectByConfig(self.reference_networks[
+                        0]['5g'])
+                    connect_result = self.dut.ed.pop_event(
+                        wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 1)
+                    self.log.info(connect_result)
+                    time.sleep(wait_time)
+                self.dut.droid.wifiConnectByConfig(self.other_network)
+                connect_result = self.dut.ed.pop_event(
+                    wifi_constants.CONNECT_BY_CONFIG_SUCCESS)
+                self.log.info(connect_result)
+                wutils.track_connection(self.dut, self.other_network["ssid"], 1)
+                wutils.wifi_forget_network(self.dut, self.other_network["ssid"])
+                time.sleep(wait_time)
+                current_network = self.dut.droid.wifiGetConnectionInfo()
+                self.log.info("Current network: {}".format(current_network))
+                asserts.assert_true('network_id' in current_network,
+                                    NETWORK_ID_ERROR)
+                asserts.assert_true(current_network['network_id'] >= 0,
+                                    NETWORK_ERROR)
+            finally:
+                self.dut.droid.wifiLockRelease()
+                self.dut.droid.goToSleepNow()
+
+    def check_connection(self, network_bssid):
+        """Check current wifi connection networks.
+        Args:
+            network_bssid: Network bssid to which connection.
+        Returns:
+            True if connection to given network happen, else return False.
+        """
+        time.sleep(40)  #time for connection state to be updated
+        self.log.info("Check network for {}".format(network_bssid))
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.debug("Current network:  {}".format(current_network))
+        if WifiEnums.BSSID_KEY in current_network:
+            return current_network[WifiEnums.BSSID_KEY] == network_bssid
+        return False
+
+    def set_attn_and_validate_connection(self, attn_value, bssid):
+        """Validate wifi connection status on different attenuation setting.
+
+        Args:
+            attn_value: Attenuation value for different APs signal.
+            bssid: Bssid of excepted network.
+
+        Returns:
+            True if bssid of current network match, else false.
+        """
+        self.attenuators[0].set_atten(attn_value[0])
+        self.attenuators[1].set_atten(attn_value[1])
+        self.attenuators[2].set_atten(attn_value[2])
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        try:
+            asserts.assert_true(
+                self.check_connection(bssid),
+                "Device is not connected to required bssid {}".format(bssid))
+            time.sleep(10)  #wait for connection to be active
+            asserts.assert_true(
+                wutils.validate_connection(self.dut, self.ping_addr),
+                "Error, No Internet connection for current bssid {}".format(
+                    bssid))
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    def on_fail(self, test_name, begin_time):
+        if self.max_bugreports > 0:
+            self.dut.take_bug_report(test_name, begin_time)
+            self.max_bugreports -= 1
+        self.dut.cat_adb_log(test_name, begin_time)
+
+    """ Tests Begin """
+
+    def test_autojoin_Ap1_2g(self):
+        """Test wifi auto join functionality move in range of AP1.
+
+         1. Attenuate the signal to low range of AP1 and Ap2 not visible at all.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Ap1_2g"]
+        variance = 5
+        attenuations = ([att0 + variance * 2, att1, att2],
+                        [att0 + variance, att1, att2], [att0, att1, att2],
+                        [att0 - variance, att1, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_Ap1_2g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[0]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed,
+            "Number of test_autojoin_Ap1_2g failed {}".format(len(failed)))
+
+    def test_autojoin_Ap1_2gto5g(self):
+        """Test wifi auto join functionality move to high range.
+
+         1. Attenuate the signal to high range of AP1.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Ap1_2gto5g"]
+        variance = 5
+        attenuations = ([att0 + variance * 2, att1, att2],
+                        [att0 + variance, att1, att2], [att0, att1, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_Ap1_2gto5g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[0]["5g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed,
+            "Number of test_autojoin_Ap1_2gto5g failed {}".format(len(failed)))
+
+    def test_autojoin_in_AP1_5gto2g(self):
+        """Test wifi auto join functionality move to low range toward AP2.
+
+         1. Attenuate the signal to medium range of AP1 and low range of AP2.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["In_AP1_5gto2g"]
+        variance = 5
+        attenuations = ([att0 - variance, att1 + variance, att2],
+                        [att0, att1, att2],
+                        [att0 + variance, att1 - variance, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_in_AP1_5gto2g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[0]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed, "Number of test_autojoin_in_AP1_5gto2g failed {}".format(
+                len(failed)))
+
+    def test_autojoin_swtich_AP1toAp2(self):
+        """Test wifi auto join functionality move from low range of AP1 to better
+           range of AP2.
+
+         1. Attenuate the signal to low range of AP1 and medium range of AP2.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Swtich_AP1toAp2"]
+        variance = 5
+        attenuations = ([att0 - variance, att1 + variance, att2],
+                        [att0, att1, att2],
+                        [att0 + variance, att1 - variance, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_swtich_AP1toAp2_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[1]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed, "Number of test_autojoin_swtich_AP1toAp2 failed {}".format(
+                len(failed)))
+
+    def test_autojoin_Ap2_2gto5g(self):
+        """Test wifi auto join functionality move to high range of AP2.
+
+         1. Attenuate the signal to out range of AP1 and high range of AP2.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Ap2_2gto5g"]
+        variance = 5
+        attenuations = ([att0 - variance, att1 + variance * 2, att2],
+                        [att0, att1 + variance, att2], [att0, att1, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_Ap2_2gto5g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[1]["5g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed,
+            "Number of test_autojoin_Ap2_2gto5g failed {}".format(len(failed)))
+
+    def test_autojoin_Ap2_5gto2g(self):
+        """Test wifi auto join functionality move to low range of AP2.
+
+         1. Attenuate the signal to low range of AP2.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable.
+        """
+        att0, att1, att2 = self.atten_val["Ap2_5gto2g"]
+        variance = 5
+        attenuations = ([att0, att1 - variance, att2], [att0, att1, att2],
+                        [att0, att1 + variance, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_Ap2_5gto2g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[1]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed,
+            "Number of test_autojoin_Ap2_5gto2g failed {}".format(len(failed)))
+
+    def test_autojoin_out_of_range(self):
+        """Test wifi auto join functionality move to low range.
+
+         1. Attenuate the signal to out of range.
+         2. Wake up the device.
+         3. Start the scan.
+         4. Check that device is not connected to any network.
+        """
+        self.attenuators[0].set_atten(90)
+        self.attenuators[1].set_atten(90)
+        self.attenuators[2].set_atten(90)
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        try:
+            wutils.start_wifi_connection_scan(self.dut)
+            wifi_results = self.dut.droid.wifiGetScanResults()
+            self.log.debug("Scan result {}".format(wifi_results))
+            time.sleep(20)
+            current_network = self.dut.droid.wifiGetConnectionInfo()
+            self.log.info("Current network: {}".format(current_network))
+            asserts.assert_true(
+                ('network_id' in current_network and
+                 current_network['network_id'] == -1),
+                "Device is connected to network {}".format(current_network))
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    def test_autojoin_Ap2_2g(self):
+        """Test wifi auto join functionality move in low range of AP2.
+
+         1. Attenuate the signal to move in range of AP2 and Ap1 not visible at all.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Ap2_2g"]
+        variance = 5
+        attenuations = ([att0, att1 + variance * 2, att2],
+                        [att0, att1 + variance, att2], [att0, att1, att2],
+                        [att0, att1 - variance, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_Ap2_2g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[1]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed,
+            "Number of test_autojoin_Ap2_2g failed {}".format(len(failed)))
+
+    def test_autojoin_in_Ap2_5gto2g(self):
+        """Test wifi auto join functionality move to medium range of Ap2 and
+           low range of AP1.
+
+         1. Attenuate the signal to move in medium range of AP2 and low range of AP1.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["In_Ap2_5gto2g"]
+        variance = 5
+        attenuations = ([att0, att1 - variance, att2], [att0, att1, att2],
+                        [att0, att1 + variance, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_in_Ap2_5gto2g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[1]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed, "Number of test_autojoin_in_Ap2_5gto2g failed {}".format(
+                len(failed)))
+
+    def test_autojoin_swtich_AP2toAp1(self):
+        """Test wifi auto join functionality move from low range of AP2 to better
+           range of AP1.
+
+         1. Attenuate the signal to low range of AP2 and medium range of AP1.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Swtich_AP2toAp1"]
+        variance = 5
+        attenuations = ([att0 + variance, att1 - variance, att2],
+                        [att0, att1, att2],
+                        [att0 - variance, att1 + variance, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_swtich_AP2toAp1_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[0]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed, "Number of test_autojoin_swtich_AP2toAp1 failed {}".format(
+                len(failed)))
+
+    def test_autojoin_Ap1_5gto2g(self):
+        """Test wifi auto join functionality move to medium range of AP1.
+
+         1. Attenuate the signal to medium range of AP1.
+         2. Wake up the device.
+         3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+        """
+        att0, att1, att2 = self.atten_val["Ap1_5gto2g"]
+        variance = 5
+        attenuations = ([att0, att1, att2], [att0 + variance, att1, att2],
+                        [att0 + variance * 2, att1, att2])
+        name_func = lambda att_value, bssid: ("test_autojoin_Ap1_5gto2g_AP1_{}_AP2"
+                                              "_{}_AP3_{}").format(att_value[0], att_value[1], att_value[2])
+        failed = self.run_generated_testcases(
+            self.set_attn_and_validate_connection,
+            attenuations,
+            args=(self.reference_networks[0]["2g"]['bssid'], ),
+            name_func=name_func)
+        asserts.assert_false(
+            failed,
+            "Number of test_autojoin_Ap1_5gto2g failed {}".format(len(failed)))
+
+    def test_autojoin_swtich_to_blacklist_AP(self):
+        """Test wifi auto join functionality in medium range of blacklist BSSID.
+
+         1. Attenuate the signal to low range of AP1 and medium range of AP3.
+         2. Wake up the device.
+         3. Check that device is connected to AP1 BSSID and maintain stable
+            connection to BSSID.
+        """
+        self.set_attn_and_validate_connection(
+            self.atten_val["Swtich_to_blacklist"],
+            self.reference_networks[0]["2g"]['bssid'])
+
+    def test_autojoin_in_blacklist_AP(self):
+        """Test wifi auto join functionality in high range of blacklist BSSID.
+
+         1. Attenuate the signal to out of range of AP1 and full range of AP3.
+         2. Wake up the device.
+         3. Check that device is disconnected form all AP.
+        """
+        attn0, attn1, attn2 = self.atten_val["In_blacklist"]
+        self.attenuators[0].set_atten(attn0)
+        self.attenuators[1].set_atten(attn1)
+        self.attenuators[2].set_atten(attn2)
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        try:
+            wutils.start_wifi_connection_scan(self.dut)
+            wifi_results = self.dut.droid.wifiGetScanResults()
+            self.log.debug("Scan result {}".format(wifi_results))
+            time.sleep(20)
+            current_network = self.dut.droid.wifiGetConnectionInfo()
+            self.log.info("Current network: {}".format(current_network))
+            asserts.assert_true(
+                ('network_id' in current_network and
+                 current_network['network_id'] == -1),
+                "Device is still connected to blacklisted network {}".format(
+                    current_network))
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    def test_autojoin_back_from_blacklist_AP(self):
+        """Test wifi auto join functionality in medium range of blacklist BSSID.
+
+         1. Attenuate the signal to medium of range of AP1 and low range of AP3.
+         2. Wake up the device.
+         3. Check that device is disconnected form all AP.
+        """
+        self.set_attn_and_validate_connection(
+            self.atten_val["Back_from_blacklist"],
+            self.reference_networks[0]["2g"]['bssid'])
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
new file mode 100755
index 0000000..ae69271
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
@@ -0,0 +1,410 @@
+#   !/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import re
+from acts import asserts
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.libs.ota import ota_updater
+import acts.signals as signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts.utils as utils
+
+WifiEnums = wutils.WifiEnums
+SSID = WifiEnums.SSID_KEY
+PWD = WifiEnums.PWD_KEY
+NETID = WifiEnums.NETID_KEY
+# 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 WifiAutoUpdateTest(WifiBaseTest):
+    """Tests for APIs in Android's WifiManager class.
+
+    Test Bed Requirement:
+    * One Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+
+    def __init__(self, controllers):
+        WifiBaseTest.__init__(self, controllers)
+        self.tests = (
+            "test_check_wifi_state_after_au",
+            "test_verify_networks_after_au",
+            "test_configstore_after_au",
+            "test_mac_randomization_after_au",
+            "test_wifi_hotspot_5g_psk_after_au",
+            "test_all_networks_connectable_after_au",
+            "test_connect_to_network_suggestion_after_au",
+            "test_check_wifi_toggling_after_au",
+            "test_connection_to_new_networks",
+            "test_reset_wifi_after_au")
+
+    def setup_class(self):
+        super(WifiAutoUpdateTest, self).setup_class()
+        ota_updater.initialize(self.user_params, self.android_devices)
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_toggle_state(self.dut, True)
+
+        # configure APs
+        self.legacy_configure_ap_and_start(wpa_network=True)
+        self.wpapsk_2g = self.reference_networks[0]["2g"]
+        self.wpapsk_5g = self.reference_networks[0]["5g"]
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
+
+        # saved & connected networks, network suggestions
+        # and new networks
+        self.saved_networks = [self.open_2g]
+        self.network_suggestions = [self.wpapsk_5g]
+        self.connected_networks = [self.wpapsk_2g, self.open_5g]
+        self.new_networks = [self.reference_networks[1]["2g"],
+                             self.open_network[1]["5g"]]
+
+        # add pre ota upgrade configuration
+        self.wifi_config_list = []
+        self.pre_default_mac = {}
+        self.pre_random_mac = {}
+        self.pst_default_mac = {}
+        self.pst_random_mac = {}
+        self.add_pre_update_configuration()
+
+        # Run OTA below, if ota fails then abort all tests.
+        try:
+            ota_updater.update(self.dut)
+        except Exception as e:
+            raise signals.TestAbortClass(
+                "Failed up apply OTA update. Aborting tests: %s" % e)
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    ### Helper Methods
+
+    def add_pre_update_configuration(self):
+        self.add_network_suggestions(self.network_suggestions)
+        self.add_network_and_enable(self.saved_networks[0])
+        self.add_wifi_hotspot()
+        self.connect_to_multiple_networks(self.connected_networks)
+
+    def add_wifi_hotspot(self):
+        self.wifi_hotspot = {"SSID": "hotspot_%s" % utils.rand_ascii_str(6),
+                             "password": "pass_%s" % utils.rand_ascii_str(6)}
+        band = WIFI_CONFIG_APBAND_5G
+        if self.dut.build_info["build_id"].startswith("Q"):
+            band = WifiEnums.WIFI_CONFIG_APBAND_5G_OLD
+            self.wifi_hotspot[WifiEnums.AP_BAND_KEY] = band
+            asserts.assert_true(
+                self.dut.droid.wifiSetWifiApConfiguration(self.wifi_hotspot),
+                "Failed to set WifiAp Configuration")
+            wifi_ap = self.dut.droid.wifiGetApConfiguration()
+            asserts.assert_true(
+                wifi_ap[WifiEnums.SSID_KEY] == self.wifi_hotspot[WifiEnums.SSID_KEY],
+                "Hotspot SSID doesn't match with expected SSID")
+            return
+        wutils.save_wifi_soft_ap_config(self.dut, self.wifi_hotspot, band)
+
+    def verify_wifi_hotspot(self):
+        """Verify wifi tethering."""
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        wutils.connect_to_wifi_network(self.dut_client,
+                                       self.wifi_hotspot,
+                                       check_connectivity=False)
+        wutils.stop_wifi_tethering(self.dut)
+
+    def connect_to_multiple_networks(self, networks):
+        """Connect to a list of wifi networks.
+
+        Args:
+            networks : list of wifi networks.
+        """
+        self.log.info("Connect to multiple wifi networks")
+        for network in networks:
+            ssid = network[SSID]
+            wutils.start_wifi_connection_scan_and_ensure_network_found(
+                self.dut, ssid)
+            wutils.wifi_connect(self.dut, network, num_of_tries=6)
+            self.wifi_config_list.append(network)
+            self.pre_default_mac[network[SSID]] = self.get_sta_mac_address()
+            self.pre_random_mac[network[SSID]] = \
+                self.dut.droid.wifigetRandomizedMacAddress(network)
+
+    def get_sta_mac_address(self):
+        """Gets the current MAC address being used for client mode."""
+        out = self.dut.adb.shell("ifconfig wlan0")
+        res = re.match(".* HWaddr (\S+).*", out, re.S)
+        return res.group(1)
+
+    def add_network_suggestions(self, network_suggestions):
+        """Add wifi network suggestions to DUT.
+
+        Args:
+            network_suggestions : suggestions to add.
+        """
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions(network_suggestions),
+            "Failed to add suggestions")
+
+        # Enable suggestions by the app.
+        self.dut.log.debug("Enabling suggestions from test")
+        self.dut.adb.shell(
+            "cmd wifi network-suggestions-set-user-approved %s yes" % \
+                SL4A_APK_NAME)
+
+    def remove_suggestions_and_ensure_no_connection(self,
+                                                    network_suggestions,
+                                                    expected_ssid):
+        """Remove network suggestions.
+
+        Args:
+            network_suggestions : suggestions to remove.
+            expected_ssid : SSID to verify that DUT is not connected.
+        """
+        # remove network suggestion and verify disconnect
+        self.dut.log.info("Removing network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiRemoveNetworkSuggestions(network_suggestions),
+            "Failed to remove suggestions")
+
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.ed.clear_all_events()
+
+        # Now ensure that we didn't connect back.
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut,
+                                    expected_ssid,
+                                    assert_on_fail=False),
+            "Device should not connect back")
+
+    def add_network_and_enable(self, network):
+        """Add a network and enable it.
+
+        Args:
+            network : Network details for the network to be added.
+        """
+        self.log.info("Add a wifi network and enable it")
+        ret = self.dut.droid.wifiAddNetwork(network)
+        asserts.assert_true(ret != -1, "Add network %r failed" % network)
+        self.wifi_config_list.append({SSID: network[SSID], NETID: ret})
+        self.dut.droid.wifiEnableNetwork(ret, 0)
+
+    def check_networks_after_autoupdate(self, networks):
+        """Verify that all previously configured networks are persistent.
+
+        Args:
+            networks: List of network dicts.
+        """
+        network_info = self.dut.droid.wifiGetConfiguredNetworks()
+        if len(network_info) != len(networks):
+            msg = (
+                "Number of configured networks before and after Auto-update "
+                "don't match. \nBefore reboot = %s \n After reboot = %s" %
+                (networks, network_info))
+            raise signals.TestFailure(msg)
+
+        # For each network, check if it exists in configured list after Auto-
+        # update.
+        for network in networks:
+            exists = wutils.match_networks({SSID: network[SSID]}, network_info)
+            if not exists:
+                raise signals.TestFailure("%s network is not present in the"
+                                          " configured list after Auto-update" %
+                                          network[SSID])
+            # Get the new network id for each network after reboot.
+            network[NETID] = exists[0]["networkId"]
+
+    def get_enabled_network(self, network1, network2):
+        """Check network status and return currently unconnected network.
+
+        Args:
+            network1: dict representing a network.
+            network2: dict representing a network.
+
+        Returns:
+            Network dict of the unconnected network.
+        """
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        enabled = network1
+        if wifi_info[SSID] == network1[SSID]:
+            enabled = network2
+        return enabled
+
+    ### Tests
+
+    @test_tracker_info(uuid="9ff1f01e-e5ff-408b-9a95-29e87a2df2d8")
+    @WifiBaseTest.wifi_test_wrap
+    def test_check_wifi_state_after_au(self):
+        """Check if the state of WiFi is enabled after Auto-update."""
+        if not self.dut.droid.wifiCheckState():
+            raise signals.TestFailure("WiFi is disabled after Auto-update!!!")
+
+    @test_tracker_info(uuid="e3ebdbba-71dd-4281-aef8-5b3d42b88770")
+    @WifiBaseTest.wifi_test_wrap
+    def test_verify_networks_after_au(self):
+        """Check if the previously added networks are intact.
+
+           Steps:
+               Number of networs should be the same and match each network.
+
+        """
+        self.check_networks_after_autoupdate(self.wifi_config_list)
+
+    @test_tracker_info(uuid="799e83c2-305d-4510-921e-dac3c0dbb6c5")
+    @WifiBaseTest.wifi_test_wrap
+    def test_configstore_after_au(self):
+        """Verify DUT automatically connects to wifi networks after ota.
+
+           Steps:
+               1. Connect to two wifi networks pre ota.
+               2. Verify DUT automatically connects to 1 after ota.
+               3. Re-connect to the other wifi network.
+        """
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        self.pst_default_mac[wifi_info[SSID]] = self.get_sta_mac_address()
+        self.pst_random_mac[wifi_info[SSID]] = \
+            self.dut.droid.wifigetRandomizedMacAddress(wifi_info)
+        reconnect_to = self.get_enabled_network(self.wifi_config_list[1],
+                                                self.wifi_config_list[2])
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, reconnect_to[SSID])
+        wutils.wifi_connect_by_id(self.dut, reconnect_to[NETID])
+        connect_data = self.dut.droid.wifiGetConnectionInfo()
+        connect_ssid = connect_data[SSID]
+        self.log.info("Expected SSID = %s" % reconnect_to[SSID])
+        self.log.info("Connected SSID = %s" % connect_ssid)
+        if connect_ssid != reconnect_to[SSID]:
+            raise signals.TestFailure(
+                "Device failed to reconnect to the correct"
+                " network after reboot.")
+        self.pst_default_mac[wifi_info[SSID]] = self.get_sta_mac_address()
+        self.pst_random_mac[wifi_info[SSID]] = \
+            self.dut.droid.wifigetRandomizedMacAddress(wifi_info)
+
+        for network in self.connected_networks:
+            wutils.wifi_forget_network(self.dut, network[SSID])
+
+    @test_tracker_info(uuid="e26d0ed9-9457-4a95-a962-4d43b0032bac")
+    @WifiBaseTest.wifi_test_wrap
+    def test_mac_randomization_after_au(self):
+        """Verify randomized MAC addrs are persistent after ota.
+
+           Steps:
+               1. Reconnect to the wifi networks configured pre ota.
+               2. Get the randomized MAC addrs.
+        """
+        for ssid, mac in self.pst_random_mac.items():
+            asserts.assert_true(
+                self.pre_random_mac[ssid] == mac,
+                "MAC addr of %s is %s after ota. Expected %s" %
+                (ssid, mac, self.pre_random_mac[ssid]))
+
+    @test_tracker_info(uuid="f68a65e6-97b7-4746-bad8-4c206551d87e")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_hotspot_5g_psk_after_au(self):
+        """Verify hotspot after ota upgrade.
+
+           Steps:
+               1. Start wifi hotspot on the saved config.
+               2. Verify DUT client connects to it.
+        """
+        self.verify_wifi_hotspot()
+
+    @test_tracker_info(uuid="21f91372-88a6-44b9-a4e8-d4664823dffb")
+    @WifiBaseTest.wifi_test_wrap
+    def test_connect_to_network_suggestion_after_au(self):
+        """Verify connection to network suggestion after ota.
+
+           Steps:
+               1. DUT has network suggestion added before OTA.
+               2. Wait for the device to connect to it.
+               3. Remove the suggestions and ensure the device does not
+                  connect back.
+        """
+        wutils.reset_wifi(self.dut)
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        wutils.wait_for_connect(self.dut, self.network_suggestions[0][SSID])
+        self.remove_suggestions_and_ensure_no_connection(
+            self.network_suggestions, self.network_suggestions[0][SSID])
+
+    @test_tracker_info(uuid="b8e47a4f-62fe-4a0e-b999-27ae1ebf4d19")
+    @WifiBaseTest.wifi_test_wrap
+    def test_connection_to_new_networks(self):
+        """Check if we can connect to new networks after Auto-update.
+
+           Steps:
+               1. Connect to a PSK network.
+               2. Connect to an open network.
+               3. Forget ntworks added in 1 & 2.
+               TODO: (@bmahadev) Add WEP network once it's ready.
+        """
+        for network in self.new_networks:
+            wutils.connect_to_wifi_network(self.dut, network)
+        for network in self.new_networks:
+            wutils.wifi_forget_network(self.dut, network[SSID])
+
+    @test_tracker_info(uuid="1d8309e4-d5a2-4f48-ba3b-895a58c9bf3a")
+    @WifiBaseTest.wifi_test_wrap
+    def test_all_networks_connectable_after_au(self):
+        """Check if previously added networks are connectable.
+
+           Steps:
+               1. Connect to previously added PSK network using network id.
+               2. Connect to previously added open network using network id.
+               TODO: (@bmahadev) Add WEP network once it's ready.
+        """
+        network = self.wifi_config_list[0]
+        if not wutils.connect_to_wifi_network_with_id(self.dut,
+                                                      network[NETID],
+                                                      network[SSID]):
+            raise signals.TestFailure("Failed to connect to %s after OTA" %
+                                      network[SSID])
+        wutils.wifi_forget_network(self.dut, network[SSID])
+
+    @test_tracker_info(uuid="05671859-38b1-4dbf-930c-18048971d075")
+    @WifiBaseTest.wifi_test_wrap
+    def test_check_wifi_toggling_after_au(self):
+        """Check if WiFi can be toggled ON/OFF after auto-update."""
+        self.log.debug("Going from on to off.")
+        wutils.wifi_toggle_state(self.dut, False)
+        self.log.debug("Going from off to on.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+    @test_tracker_info(uuid="440edf32-4b00-42b0-9811-9f2bc4a83efb")
+    @WifiBaseTest.wifi_test_wrap
+    def test_reset_wifi_after_au(self):
+        """"Check if WiFi can be reset after auto-update."""
+        wutils.reset_wifi(self.dut)
diff --git a/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py b/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
new file mode 100644
index 0000000..bdba6e1
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
@@ -0,0 +1,308 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import random
+import re
+import logging
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.test_utils.tel.tel_test_utils as tel_utils
+import acts.utils as utils
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts import signals
+from acts.controllers import packet_capture
+from acts.controllers.ap_lib.hostapd_constants import BAND_2G
+from acts.controllers.ap_lib.hostapd_constants import BAND_5G
+
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiChannelSwitchStressTest(WifiBaseTest):
+
+    def setup_class(self):
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+        utils.require_sl4a((self.dut, self.dut_client))
+
+        if hasattr(self, 'packet_capture'):
+            self.packet_capture = self.packet_capture[0]
+
+        req_params = ["dbs_supported_models"]
+        opt_param = ["stress_count", "cs_count"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        self.AP_IFACE = 'wlan0'
+        if self.dut.model in self.dbs_supported_models:
+            self.AP_IFACE = 'wlan1'
+
+        for ad in self.android_devices:
+            wutils.wifi_test_device_init(ad)
+            utils.sync_device_time(ad)
+            wutils.set_wifi_country_code(ad, WifiEnums.CountryCode.US)
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+            try:
+                if self.dut.droid.wifiIsApEnabled():
+                    wutils.stop_wifi_tethering(self.dut)
+            except signals.TestFailure:
+                pass
+        wutils.wifi_toggle_state(self.dut_client, True)
+        init_sim_state = tel_utils.is_sim_ready(self.log, self.dut)
+        if init_sim_state:
+            self.check_cell_data_and_enable()
+        self.config = wutils.create_softap_config()
+        self.channel_list_2g = self.generate_random_list(
+            WifiEnums.ALL_2G_FREQUENCIES)
+        self.channel_list_5g = self.generate_random_list(
+            WifiEnums.NONE_DFS_5G_FREQUENCIES)
+
+    def teardown_test(self):
+        super().teardown_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+            wutils.reset_wifi(ad)
+        try:
+            wutils.stop_wifi_tethering(self.dut)
+        except signals.TestFailure:
+            pass
+
+    def on_fail(self, test_name, begin_time):
+        try:
+            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        except signals.TestFailure:
+            pass
+        super().on_fail(test_name, begin_time)
+
+    def check_cell_data_and_enable(self):
+        """Make sure that cell data is enabled if there is a sim present.
+
+        If a sim is active, cell data needs to be enabled to allow provisioning
+        checks through (when applicable).  This is done to relax hardware
+        requirements on DUTs - without this check, running this set of tests
+        after other wifi tests may cause failures.
+        """
+        if not self.dut.droid.telephonyIsDataEnabled():
+            self.dut.log.info("need to enable data")
+            self.dut.droid.telephonyToggleDataConnection(True)
+            asserts.assert_true(self.dut.droid.telephonyIsDataEnabled(),
+                                "Failed to enable cell data for dut.")
+
+    def get_wlan0_link(self, dut):
+        """ get wlan0 interface status"""
+        get_wlan0 = 'wpa_cli -iwlan0 -g@android:wpa_wlan0 IFNAME=wlan0 status'
+        out = dut.adb.shell(get_wlan0)
+        out = dict(re.findall(r'(\S+)=(".*?"|\S+)', out))
+        asserts.assert_true("ssid" in out,
+                            "Client doesn't connect to any network")
+        return out
+
+    def get_wlan1_status(self, dut):
+        """ get wlan1 interface status"""
+        get_wlan1 = 'hostapd_cli status'
+        out_wlan1 = dut.adb.shell(get_wlan1)
+        out_wlan1 = dict(re.findall(r'(\S+)=(".*?"|\S+)', out_wlan1))
+        return out_wlan1
+
+    def generate_random_list(self, lst):
+        """Generate a list where
+        the previous and subsequent items
+        do not repeat"""
+        channel_list = []
+        num = random.choice(lst)
+        channel_list.append(num)
+        for i in range(1, self.stress_count):
+            num = random.choice(lst)
+            while num == channel_list[-1]:
+                num = random.choice(lst)
+            channel_list.append(num)
+        return channel_list
+
+    def conf_packet_capture(self, band, channel):
+        """Configure packet capture on necessary channels."""
+        freq_to_chan = wutils.WifiEnums.freq_to_channel[int(channel)]
+        logging.info("Capturing packets from "
+                     "frequency:{}, Channel:{}".format(channel, freq_to_chan))
+        result = self.packet_capture.configure_monitor_mode(band, freq_to_chan)
+        if not result:
+            logging.error("Failed to configure channel "
+                          "for {} band".format(band))
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, band, self.test_name)
+
+        time.sleep(5)
+
+    @test_tracker_info(uuid="b1a8b00e-eca8-4eba-99e9-c7a3beb2a009")
+    def test_softap_channel_switch_stress_2g(self):
+        """
+        1. Disable DUT's Wi-Fi
+        2. Enable CLIENT's Wi-Fi
+        3. Check DUT's sim is ready or not
+        4. Enable DUT's mobile data
+        5. Bring up DUT's softap in 2g
+        6. CLIENT connect to DUT
+        7. DUT switch to different 2g channel
+        8. Verify CLIENT follow the change
+        9, Repeat step 7 and 8
+        """
+        wutils.start_wifi_tethering(self.dut,
+                                    self.config[wutils.WifiEnums.SSID_KEY],
+                                    self.config[wutils.WifiEnums.PWD_KEY],
+                                    WifiEnums.WIFI_CONFIG_APBAND_2G)
+        wutils.connect_to_wifi_network(self.dut_client, self.config)
+        time.sleep(10)
+        for count in range(len(self.channel_list_2g)):
+            self.dut.log.info("2g channel switch iteration : {}".format(count+1))
+            channel_2g = str(self.channel_list_2g[count])
+            # Configure sniffer before set SoftAP channel
+            if hasattr(self, 'packet_capture'):
+                self.conf_packet_capture(BAND_2G, channel_2g)
+            # Set SoftAP channel
+            wutils.set_softap_channel(self.dut,
+                                      self.AP_IFACE,
+                                      self.cs_count, channel_2g)
+            time.sleep(10)
+            softap_frequency = int(self.get_wlan1_status(self.dut)['freq'])
+            self.dut.log.info('softap frequency : {}'.format(softap_frequency))
+            client_frequency = int(self.get_wlan0_link(self.dut_client)["freq"])
+            self.dut_client.log.info(
+                "client frequency : {}".format(client_frequency))
+            asserts.assert_true(
+                softap_frequency == client_frequency,
+                "hotspot frequency != client frequency")
+            if hasattr(self, 'packet_capture'):
+                wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+
+    @test_tracker_info(uuid="3411cb7c-2609-433a-97b6-202a096dc71b")
+    def test_softap_channel_switch_stress_5g(self):
+        """
+        1. Disable DUT's Wi-Fi
+        2. Enable CLIENT's Wi-Fi
+        3. Check DUT's sim is ready or not
+        4. Enable DUT's mobile data
+        5. Bring up DUT's softap in 5g
+        6. CLIENT connect to DUT
+        7. DUT switch to different 5g channel
+        8. Verify CLIENT follow the change
+        9, Repeat step 7 and 8
+        """
+        wutils.start_wifi_tethering(self.dut,
+                                    self.config[wutils.WifiEnums.SSID_KEY],
+                                    self.config[wutils.WifiEnums.PWD_KEY],
+                                    WifiEnums.WIFI_CONFIG_APBAND_5G)
+        wutils.connect_to_wifi_network(self.dut_client, self.config)
+        time.sleep(10)
+        for count in range(len(self.channel_list_5g)):
+            self.dut.log.info("5g channel switch iteration : {}".format(count+1))
+            channel_5g = str(self.channel_list_5g[count])
+            # Configure sniffer before set SoftAP channel
+            if hasattr(self, 'packet_capture'):
+                self.conf_packet_capture(BAND_5G, channel_5g)
+            # Set SoftAP channel
+            wutils.set_softap_channel(self.dut,
+                                      self.AP_IFACE,
+                                      self.cs_count, channel_5g)
+            time.sleep(10)
+            softap_frequency = int(self.get_wlan1_status(self.dut)['freq'])
+            self.dut.log.info('softap frequency : {}'.format(softap_frequency))
+            client_frequency = int(self.get_wlan0_link(self.dut_client)["freq"])
+            self.dut_client.log.info(
+                "client frequency : {}".format(client_frequency))
+            asserts.assert_true(
+                softap_frequency == client_frequency,
+                "hotspot frequency != client frequency")
+            if hasattr(self, 'packet_capture'):
+                wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+
+    @test_tracker_info(uuid="0f279058-119f-49fc-b8d6-fb2991cc35aa")
+    def test_softap_channel_switch_stress_2g_5g(self):
+        """
+        1. Disable DUT's Wi-Fi
+        2. Enable CLIENT's Wi-Fi
+        3. Check DUT's sim is ready or not
+        4. Enable DUT's mobile data
+        5. Bring up DUT's softap in 2g
+        6. CLIENT connect to DUT
+        7. DUT switch to different 2g channel
+        8. Verify CLIENT follow the change
+        9. DUT switch to 5g channel
+        10. Verify CLIENT follow the change
+        11. Repeat step 7 to 10
+        """
+        for count in range(self.stress_count):
+            self.log.info("2g 5g channel switch iteration : {}".format(count+1))
+            wutils.start_wifi_tethering(self.dut,
+                                        self.config[wutils.WifiEnums.SSID_KEY],
+                                        self.config[wutils.WifiEnums.PWD_KEY],
+                                        WifiEnums.WIFI_CONFIG_APBAND_2G)
+            wutils.connect_to_wifi_network(self.dut_client, self.config)
+            self.log.info("wait 10 secs for client reconnect to dut")
+            time.sleep(10)
+            channel_2g = self.channel_list_2g[count]
+            if hasattr(self, 'packet_capture'):
+                self.conf_packet_capture(BAND_2G, channel_2g)
+            wutils.set_softap_channel(self.dut,
+                                      self.AP_IFACE,
+                                      self.cs_count, channel_2g)
+            time.sleep(10)
+            softap_frequency = int(self.get_wlan1_status(self.dut)['freq'])
+            self.dut.log.info('softap frequency : {}'.format(softap_frequency))
+            client_frequency = int(self.get_wlan0_link(self.dut_client)["freq"])
+            self.dut_client.log.info(
+                "client frequency : {}".format(client_frequency))
+            asserts.assert_true(
+                softap_frequency == client_frequency,
+                "hotspot frequency != client frequency")
+            wutils.stop_wifi_tethering(self.dut)
+            if hasattr(self, 'packet_capture'):
+                wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+            self.dut.log.info('switch to SoftAP 5g')
+
+            # switch to SoftAp 5g
+            wutils.start_wifi_tethering(self.dut,
+                                        self.config[wutils.WifiEnums.SSID_KEY],
+                                        self.config[wutils.WifiEnums.PWD_KEY],
+                                        WifiEnums.WIFI_CONFIG_APBAND_5G)
+            wutils.connect_to_wifi_network(self.dut_client, self.config)
+            self.log.info("wait 10 secs for client reconnect to dut")
+            time.sleep(10)
+            channel_5g = self.channel_list_5g[count]
+            if hasattr(self, 'packet_capture'):
+                self.conf_packet_capture(BAND_5G, channel_5g)
+            wutils.set_softap_channel(self.dut,
+                                      self.AP_IFACE,
+                                      self.cs_count, channel_5g)
+            time.sleep(10)
+            softap_frequency = int(self.get_wlan1_status(self.dut)['freq'])
+            self.dut.log.info('softap frequency : {}'.format(softap_frequency))
+            client_frequency = int(self.get_wlan0_link(self.dut_client)["freq"])
+            self.dut_client.log.info(
+                "client frequency : {}".format(client_frequency))
+            asserts.assert_true(
+                softap_frequency == client_frequency,
+                "hotspot frequency != client frequency")
+            wutils.stop_wifi_tethering(self.dut)
+            if hasattr(self, 'packet_capture'):
+                wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
diff --git a/acts_tests/tests/google/wifi/WifiChaosTest.py b/acts_tests/tests/google/wifi/WifiChaosTest.py
new file mode 100755
index 0000000..77a83ec
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiChaosTest.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import re
+import sys
+import random
+import time
+
+import acts.controllers.packet_capture as packet_capture
+import acts.signals as signals
+import acts.test_utils.wifi.rpm_controller_utils as rutils
+import acts.test_utils.wifi.wifi_datastore_utils as dutils
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.controllers.ap_lib import hostapd_constants
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+WAIT_BEFORE_CONNECTION = 1
+SINGLE_BAND = 1
+DUAL_BAND = 2
+
+TIMEOUT = 60
+TEST = 'test_'
+PING_ADDR = 'www.google.com'
+
+NUM_LINK_PROBES = 3
+PROBE_DELAY_SEC = 1
+
+
+class WifiChaosTest(WifiBaseTest):
+    """ Tests for wifi IOT
+
+        Test Bed Requirement:
+          * One Android device
+          * Wi-Fi IOT networks visible to the device
+    """
+
+    def __init__(self, configs):
+        BaseTestClass.__init__(self, configs)
+        self.generate_interop_tests()
+
+    def randomize_testcases(self):
+        """Randomize the list of hosts and build a random order of tests,
+           based on SSIDs, keeping all the relevant bands of an AP together.
+
+        """
+        temp_tests = list()
+        hosts = self.user_params['interop_host']
+
+        random.shuffle(hosts)
+
+        for host in hosts:
+            ssid_2g = None
+            ssid_5g = None
+            info = dutils.show_device(host)
+
+            # Based on the information in datastore, add to test list if
+            # AP has 2G band.
+            if 'ssid_2g' in info:
+                ssid_2g = info['ssid_2g']
+                temp_tests.append(TEST + ssid_2g)
+
+            # Based on the information in datastore, add to test list if
+            # AP has 5G band.
+            if 'ssid_5g' in info:
+                ssid_5g = info['ssid_5g']
+                temp_tests.append(TEST + ssid_5g)
+
+        self.tests = temp_tests
+
+    def generate_interop_testcase(self, base_test, testcase_name, ssid_dict):
+        """Generates a single test case from the given data.
+
+        Args:
+            base_test: The base test case function to run.
+            testcase_name: The name of the test case.
+            ssid_dict: The information about the network under test.
+        """
+        ssid = testcase_name
+        test_tracker_uuid = ssid_dict[testcase_name]['uuid']
+        hostname = ssid_dict[testcase_name]['host']
+        if not testcase_name.startswith('test_'):
+            testcase_name = 'test_%s' % testcase_name
+        test_case = test_tracker_info(uuid=test_tracker_uuid)(
+            lambda: base_test(ssid, hostname))
+        setattr(self, testcase_name, test_case)
+        self.tests.append(testcase_name)
+
+    def generate_interop_tests(self):
+        for ssid_dict in self.user_params['interop_ssid']:
+            testcase_name = list(ssid_dict)[0]
+            self.generate_interop_testcase(self.interop_base_test,
+                                           testcase_name, ssid_dict)
+        self.randomize_testcases()
+
+    def setup_class(self):
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        self.admin = 'admin' + str(random.randint(1000001, 12345678))
+        wutils.wifi_test_device_init(self.dut)
+        # Set country code explicitly to "US".
+        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
+
+        asserts.assert_true(
+            self.lock_pcap(),
+            "Could not lock a Packet Capture. Aborting Interop test.")
+
+        wutils.wifi_toggle_state(self.dut, True)
+
+    def lock_pcap(self):
+        """Lock a Packet Capturere to use for the test."""
+
+        # Get list of devices from the datastore.
+        locked_pcap = False
+        devices = dutils.get_devices()
+
+        for device in devices:
+
+            device_name = device['hostname']
+            device_type = device['ap_label']
+            if device_type == 'PCAP' and not device['lock_status']:
+                if dutils.lock_device(device_name, self.admin):
+                    self.pcap_host = device_name
+                    host = device['ip_address']
+                    self.log.info("Locked Packet Capture device: %s" % device_name)
+                    locked_pcap = True
+                    break
+                else:
+                    self.log.warning("Failed to lock %s PCAP." % device_name)
+
+        if not locked_pcap:
+            return False
+
+        pcap_config = {'ssh_config':{'user':'root'} }
+        pcap_config['ssh_config']['host'] = host
+
+        self.pcap = packet_capture.PacketCapture(pcap_config)
+        return True
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def on_pass(self, test_name, begin_time):
+        wutils.stop_pcap(self.pcap, self.pcap_procs, True)
+
+    def on_fail(self, test_name, begin_time):
+        # Sleep to ensure all failed packets are captured.
+        time.sleep(5)
+        wutils.stop_pcap(self.pcap, self.pcap_procs, False)
+        super().on_fail(test_name, begin_time)
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+
+    def teardown_class(self):
+        # Unlock the PCAP.
+        if not dutils.unlock_device(self.pcap_host):
+            self.log.warning("Failed to unlock %s PCAP. Check in datastore.")
+
+
+    """Helper Functions"""
+
+    def scan_and_connect_by_id(self, network, net_id):
+        """Scan for network and connect using network id.
+
+        Args:
+            net_id: Integer specifying the network id of the network.
+
+        """
+        ssid = network[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(self.dut,
+                                                                   ssid)
+        wutils.wifi_connect_by_id(self.dut, net_id)
+
+    def run_ping(self, sec):
+        """Run ping for given number of seconds.
+
+        Args:
+            sec: Time in seconds to run teh ping traffic.
+
+        """
+        self.log.info("Finding Gateway...")
+        route_response = self.dut.adb.shell("ip route get 8.8.8.8")
+        gateway_ip = re.search('via (.*) dev', str(route_response)).group(1)
+        self.log.info("Gateway IP = %s" % gateway_ip)
+        self.log.info("Running ping for %d seconds" % sec)
+        result = self.dut.adb.shell("ping -w %d %s" % (sec, gateway_ip),
+                                    timeout=sec + 1)
+        self.log.debug("Ping Result = %s" % result)
+        if "100% packet loss" in result:
+            raise signals.TestFailure("100% packet loss during ping")
+
+    def send_link_probes(self, network):
+        """
+        Send link probes, and verify that the device and AP did not crash.
+        Also verify that at least one link probe succeeded.
+
+        Steps:
+        1. Send a few link probes.
+        2. Ensure that the device and AP did not crash (by checking that the
+           device remains connected to the expected network).
+        """
+        results = wutils.send_link_probes(
+            self.dut, NUM_LINK_PROBES, PROBE_DELAY_SEC)
+
+        self.log.info("Link Probe results: %s" % (results,))
+
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        expected = network[WifiEnums.SSID_KEY]
+        actual = wifi_info[WifiEnums.SSID_KEY]
+        asserts.assert_equal(
+            expected, actual,
+            "Device did not remain connected after sending link probes!")
+
+    def unlock_and_turn_off_ap(self, hostname, rpm_port, rpm_ip):
+        """UNlock the AP in datastore and turn off the AP.
+
+        Args:
+            hostname: Hostname of the AP.
+            rpm_port: Port number on the RPM for the AP.
+            rpm_ip: RPM's IP address.
+
+        """
+        # Un-Lock AP in datastore.
+        self.log.debug("Un-lock AP in datastore")
+        if not dutils.unlock_device(hostname):
+            self.log.warning("Failed to unlock %s AP. Check AP in datastore.")
+        # Turn OFF AP from the RPM port.
+        rutils.turn_off_ap(rpm_port, rpm_ip)
+
+    def run_connect_disconnect(self, network, hostname, rpm_port, rpm_ip,
+                               release_ap):
+        """Run connect/disconnect to a given network in loop.
+
+           Args:
+               network: Dict, network information.
+               hostname: Hostanme of the AP to connect to.
+               rpm_port: Port number on the RPM for the AP.
+               rpm_ip: Port number on the RPM for the AP.
+               release_ap: Flag to determine if we should turn off the AP yet.
+
+           Raises: TestFailure if the network connection fails.
+
+        """
+        for attempt in range(5):
+            try:
+                begin_time = time.time()
+                ssid = network[WifiEnums.SSID_KEY]
+                net_id = self.dut.droid.wifiAddNetwork(network)
+                asserts.assert_true(net_id != -1, "Add network %s failed" % network)
+                self.log.info("Connecting to %s" % ssid)
+                self.scan_and_connect_by_id(network, net_id)
+                self.run_ping(10)
+                # TODO(b/133369482): uncomment once bug is resolved
+                # self.send_link_probes(network)
+                wutils.wifi_forget_network(self.dut, ssid)
+                time.sleep(WAIT_BEFORE_CONNECTION)
+            except Exception as e:
+                self.log.error("Connection to %s network failed on the %d "
+                               "attempt with exception %s." % (ssid, attempt, e))
+                # TODO:(bmahadev) Uncomment after scan issue is fixed.
+                # self.dut.take_bug_report(ssid, begin_time)
+                # self.dut.cat_adb_log(ssid, begin_time)
+                if release_ap:
+                    self.unlock_and_turn_off_ap(hostname, rpm_port, rpm_ip)
+                raise signals.TestFailure("Failed to connect to %s" % ssid)
+
+    def get_band_and_chan(self, ssid):
+        """Get the band and channel information from SSID.
+
+        Args:
+            ssid: SSID of the network.
+
+        """
+        ssid_info = ssid.split('_')
+        self.band = ssid_info[-1]
+        for item in ssid_info:
+            # Skip over the router model part.
+            if 'ch' in item and item != ssid_info[0]:
+                self.chan = re.search(r'(\d+)',item).group(0)
+                return
+        raise signals.TestFailure("Channel information not found in SSID.")
+
+    def interop_base_test(self, ssid, hostname):
+        """Base test for all the connect-disconnect interop tests.
+
+        Args:
+            ssid: string, SSID of the network to connect to.
+            hostname: string, hostname of the AP.
+
+        Steps:
+            1. Lock AP in datstore.
+            2. Turn on AP on the rpm switch.
+            3. Run connect-disconnect in loop.
+            4. Turn off AP on the rpm switch.
+            5. Unlock AP in datastore.
+
+        """
+        network = {}
+        network['password'] = 'password'
+        network['SSID'] = ssid
+        release_ap = False
+        wutils.reset_wifi(self.dut)
+
+        # Lock AP in datastore.
+        self.log.info("Lock AP in datastore")
+
+        ap_info = dutils.show_device(hostname)
+
+        # If AP is locked by a different test admin, then we skip.
+        if ap_info['lock_status'] and ap_info['locked_by'] != self.admin:
+            raise signals.TestSkip("AP %s is locked, skipping test" % hostname)
+
+        if not dutils.lock_device(hostname, self.admin):
+            self.log.warning("Failed to lock %s AP. Unlock AP in datastore"
+                             " and try again.")
+            raise signals.TestFailure("Failed to lock AP")
+
+        band = SINGLE_BAND
+        if ('ssid_2g' in ap_info) and ('ssid_5g' in ap_info):
+            band = DUAL_BAND
+        if (band == SINGLE_BAND) or (
+                band == DUAL_BAND and '5G' in ssid):
+            release_ap = True
+
+        # Get AP RPM attributes and Turn ON AP.
+        rpm_ip = ap_info['rpm_ip']
+        rpm_port = ap_info['rpm_port']
+
+        rutils.turn_on_ap(self.pcap, ssid, rpm_port, rpm_ip=rpm_ip)
+        self.log.info("Finished turning ON AP.")
+        # Experimental. Some APs take upto a min to come online.
+        time.sleep(60)
+
+        self.get_band_and_chan(ssid)
+        self.pcap.configure_monitor_mode(self.band, self.chan)
+        self.pcap_procs = wutils.start_pcap(
+                self.pcap, self.band.lower(), self.test_name)
+        self.run_connect_disconnect(network, hostname, rpm_port, rpm_ip,
+                                    release_ap)
+
+        # Un-lock only if it's a single band AP or we are running the last band.
+        if release_ap:
+            self.unlock_and_turn_off_ap(hostname, rpm_port, rpm_ip)
diff --git a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
new file mode 100644
index 0000000..14eafab
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+import acts.base_test
+import acts.signals as signals
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils as utils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+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
+GET_MAC_ADDRESS= ("ip addr show wlan0"
+                  "| grep 'link/ether'"
+                  "| cut -d ' ' -f6")
+MAC_SETTING = "wifi_connected_mac_randomization_enabled"
+GET_MAC_RANDOMIZATION_STATUS = "settings get global {}".format(MAC_SETTING)
+TURN_ON_MAC_RANDOMIZATION = "settings put global {} 1".format(MAC_SETTING)
+TURN_OFF_MAC_RANDOMIZATION = "settings put global {} 0".format(MAC_SETTING)
+LOG_CLEAR = "logcat -c"
+LOG_GREP = "logcat -d | grep {}"
+
+class WifiConnectedMacRandomizationTest(WifiBaseTest):
+    """Tests for Connected MAC Randomization.
+
+    Test Bed Requirement:
+    * Two Android devices with the first one supporting MAC randomization.
+    * At least two Wi-Fi networks to connect to.
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        self.dut_softap = self.android_devices[1]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_test_device_init(self.dut_softap)
+
+        self.reset_mac_address_to_factory_mac()
+        self.dut.adb.shell(TURN_ON_MAC_RANDOMIZATION)
+        asserts.assert_equal(
+            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "1",
+            "Failed to enable Connected MAC Randomization on dut.")
+
+        req_params = ["reference_networks"]
+        opt_param = []
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+
+        asserts.assert_true(
+            self.reference_networks[0]["2g"],
+            "Need at least 1 2.4Ghz reference network with psk.")
+        asserts.assert_true(
+            self.reference_networks[0]["5g"],
+            "Need at least 1 5Ghz reference network with psk.")
+        self.wpapsk_2g = self.reference_networks[0]["2g"]
+        self.wpapsk_5g = self.reference_networks[0]["5g"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_toggle_state(self.dut_softap, False)
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+        wutils.reset_wifi(self.dut_softap)
+
+    def teardown_class(self):
+        wutils.stop_wifi_tethering(self.dut_softap)
+        self.reset_mac_address_to_factory_mac()
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+    def get_current_mac_address(self, ad):
+        """Get the device's wlan0 MAC address.
+
+        Args:
+            ad: AndroidDevice to get MAC address of.
+
+        Returns:
+            A MAC address string in the format of "12:34:56:78:90:12".
+        """
+        return ad.adb.shell(GET_MAC_ADDRESS)
+
+    def is_valid_randomized_mac_address(self, mac):
+        """Check if the given MAC address is a valid randomized MAC address.
+
+        Args:
+            mac: MAC address to check in the format of "12:34:56:78:90:12".
+        """
+        asserts.assert_true(
+            mac != self.dut_factory_mac,
+            "Randomized MAC address is same as factory MAC address.")
+        first_byte = int(mac[:2], 16)
+        asserts.assert_equal(first_byte & 1, 0, "MAC address is not unicast.")
+        asserts.assert_equal(first_byte & 2, 2, "MAC address is not local.")
+
+    def reset_mac_address_to_factory_mac(self):
+        """Reset dut to and store factory MAC address by turning off
+        Connected MAC Randomization and rebooting dut.
+        """
+        self.dut.adb.shell(TURN_OFF_MAC_RANDOMIZATION)
+        asserts.assert_equal(
+            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "0",
+            "Failed to disable Connected MAC Randomization on dut.")
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        self.dut_factory_mac = self.get_current_mac_address(self.dut)
+
+    def get_connection_data(self, ad, network):
+        """Connect and get network id and ssid info from connection data.
+
+        Args:
+            ad: AndroidDevice to use for connection
+            network: network info of the network to connect to
+
+        Returns:
+            A convenience dict with the connected network's ID and SSID.
+        """
+        wutils.connect_to_wifi_network(ad, network)
+        connect_data = ad.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
+
+    """Tests"""
+    @test_tracker_info(uuid="")
+    def test_wifi_connection_2G_with_mac_randomization(self):
+        """Tests connection to 2G network with Connected MAC Randomization.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpapsk_2g)
+        mac = self.get_current_mac_address(self.dut)
+        self.is_valid_randomized_mac_address(mac)
+
+    @test_tracker_info(uuid="")
+    def test_wifi_connection_5G_with_mac_randomization(self):
+        """Tests connection to 5G network with Connected MAC Randomization.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpapsk_5g)
+        mac = self.get_current_mac_address(self.dut)
+        self.is_valid_randomized_mac_address(mac)
+
+    @test_tracker_info(uuid="")
+    def test_randomized_mac_persistent_between_connections(self):
+        """Tests that randomized MAC address assigned to each network is
+        persistent between connections.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Reconnect to the 2GHz network using its network id.
+        4. Verify that MAC addresses in Steps 1 and 3 are equal.
+        5. Reconnect to the 5GHz network using its network id.
+        6. Verify that MAC addresses in Steps 2 and 5 are equal.
+        """
+        connect_data_2g = self.get_connection_data(self.dut, self.wpapsk_2g)
+        old_mac_2g = self.get_current_mac_address(self.dut)
+        self.is_valid_randomized_mac_address(old_mac_2g)
+
+        connect_data_5g = self.get_connection_data(self.dut, self.wpapsk_5g)
+        old_mac_5g = self.get_current_mac_address(self.dut)
+        self.is_valid_randomized_mac_address(old_mac_5g)
+
+        asserts.assert_true(
+            old_mac_2g != old_mac_5g,
+            "Randomized MAC addresses for 2G and 5G networks are equal.")
+
+        reconnect_2g = wutils.connect_to_wifi_network_with_id(
+            self.dut,
+            connect_data_2g[WifiEnums.NETID_KEY],
+            connect_data_2g[WifiEnums.SSID_KEY])
+        if not reconnect_2g:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                      " 2GHz network.")
+        new_mac_2g = self.get_current_mac_address(self.dut)
+        asserts.assert_equal(
+            old_mac_2g,
+            new_mac_2g,
+            "Randomized MAC for 2G is not persistent between connections.")
+
+        reconnect_5g = wutils.connect_to_wifi_network_with_id(
+            self.dut,
+            connect_data_5g[WifiEnums.NETID_KEY],
+            connect_data_5g[WifiEnums.SSID_KEY])
+        if not reconnect_5g:
+            raise signals.TestFailure("Device did not connect to the correct"
+                                      " 5GHz network.")
+        new_mac_5g = self.get_current_mac_address(self.dut)
+        asserts.assert_equal(
+            old_mac_5g,
+            new_mac_5g,
+            "Randomized MAC for 5G is not persistent between connections.")
+
+    @test_tracker_info(uuid="")
+    def test_randomized_mac_used_during_connection(self):
+        """Verify that the randomized MAC address and not the factory
+        MAC address is used during connection by checking the softap logs.
+
+        Steps:
+        1. Set up softAP on dut_softap.
+        2. Have dut connect to the softAp.
+        3. Verify that only randomized MAC appears in softAp logs.
+        """
+        self.dut_softap.adb.shell(LOG_CLEAR)
+        config = wutils.create_softap_config()
+        wutils.start_wifi_tethering(self.dut_softap,
+                                    config[wutils.WifiEnums.SSID_KEY],
+                                    config[wutils.WifiEnums.PWD_KEY],
+                                    WIFI_CONFIG_APBAND_2G)
+
+        # Internet validation fails when dut_softap does not have a valid sim
+        # supporting softap. Since this test is not checking for internet
+        # validation, we suppress failure signals.
+        wutils.connect_to_wifi_network(self.dut, config, assert_on_fail=False)
+        mac = self.get_current_mac_address(self.dut)
+        wutils.stop_wifi_tethering(self.dut_softap)
+
+        self.is_valid_randomized_mac_address(mac)
+        log = self.dut_softap.adb.shell(LOG_GREP.format(mac))
+        asserts.assert_true(len(log) > 0, "Randomized MAC not in log.")
+        log = self.dut_softap.adb.shell(LOG_GREP.format(self.dut_factory_mac))
+        asserts.assert_true(len(log) == 0, "Factory MAC is in log.")
diff --git a/acts_tests/tests/google/wifi/WifiCrashStressTest.py b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
new file mode 100644
index 0000000..503583c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.tel.tel_test_utils import disable_qxdm_logger
+
+WifiEnums = wutils.WifiEnums
+
+class WifiCrashStressTest(WifiBaseTest):
+    """Crash Tests for wifi stack.
+
+    Test Bed Requirement:
+    * Two Android device
+    * One Wi-Fi network visible to the device.
+    """
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_test_device_init(self.dut_client)
+        req_params = ["dbs_supported_models", "stress_count"]
+        opt_param = ["reference_networks"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+
+        asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+        self.network = self.reference_networks[0]["2g"]
+        self.ap_iface = 'wlan0'
+        if self.dut.model in self.dbs_supported_models:
+            self.ap_iface = 'wlan1'
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut, True)
+        self.dut_client.droid.wakeLockAcquireBright()
+        self.dut_client.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut_client, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        if self.dut.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.dut)
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+        self.dut_client.droid.wakeLockRelease()
+        self.dut_client.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut_client)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+
+    """Helper Functions"""
+    def trigger_wifi_firmware_crash(self, ad, timeout=15):
+        pre_timestamp = ad.adb.getprop("vendor.debug.ssrdump.timestamp")
+        disable_qxdm_logger(ad)
+        cmd = ('wlanSSR')
+        ad.log.info("Crash wifi firmware by %s", cmd)
+        ad.adb.shell(cmd, ignore_status=True)
+        time.sleep(timeout)  # sleep time for firmware restart
+        subsystem = ad.adb.getprop("vendor.debug.ssrdump.subsys")
+        timestamp = ad.adb.getprop("vendor.debug.ssrdump.timestamp")
+        asserts.assert_true(timestamp != pre_timestamp,
+            "SSR didn't happened %s %s" % (subsystem, timestamp))
+
+    """Tests"""
+    @test_tracker_info(uuid="b5a982ef-10ef-4f36-a1b5-1e5d1fec06a4")
+    def test_firmware_crash_wifi_reconnect_stress(self):
+        """Firmware crash stress test for station mode
+
+        1. Turn on Wi-Fi and connect to access point
+        2. Trigger firmware crash
+        3. Check ssr happened
+        4. Check dut can connect to access point
+        5. Repeat step 2~4
+        """
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.connect_to_wifi_network(self.dut, self.network)
+        for count in range(self.stress_count):
+            self.log.info("%s: %d/%d" %
+                (self.current_test_name, count + 1, self.stress_count))
+            wutils.reset_wifi(self.dut)
+            self.trigger_wifi_firmware_crash(self.dut)
+            wutils.connect_to_wifi_network(self.dut, self.network)
+
+    @test_tracker_info(uuid="204a921b-b0de-47f7-9b70-9384317051c8")
+    def test_firmware_crash_softap_reconnect_stress(self):
+        """Firmware crash stress test for softap mode
+
+        1. Turn off dut's Wi-Fi
+        2. Turn on dut's hotspot and connected by dut client
+        3. Trigger firmware crash
+        4. Check ssr happened
+        5. Check the connectivity of hotspot's client
+        6. Repeat step 3~5
+        """
+        wutils.wifi_toggle_state(self.dut, False)
+        # Setup Soft AP
+        sap_config = wutils.create_softap_config()
+        wutils.start_wifi_tethering(
+            self.dut, sap_config[wutils.WifiEnums.SSID_KEY],
+            sap_config[wutils.WifiEnums.PWD_KEY], wutils.WifiEnums.WIFI_CONFIG_APBAND_2G)
+        config = {
+            "SSID": sap_config[wutils.WifiEnums.SSID_KEY],
+            "password": sap_config[wutils.WifiEnums.PWD_KEY]
+        }
+        # DUT client connects to softap
+        wutils.wifi_toggle_state(self.dut_client, True)
+        wutils.connect_to_wifi_network(self.dut_client, config, check_connectivity=False)
+        # Ping the DUT
+        dut_addr = self.dut.droid.connectivityGetIPv4Addresses(
+                self.ap_iface)[0]
+        asserts.assert_true(
+            utils.adb_shell_ping(self.dut_client, count=10, dest_ip=dut_addr, timeout=20),
+            "%s ping %s failed" % (self.dut_client.serial, dut_addr))
+        for count in range(self.stress_count):
+            self.log.info("%s: %d/%d" %
+                (self.current_test_name, count + 1, self.stress_count))
+            wutils.reset_wifi(self.dut_client)
+            # Trigger firmware crash
+            self.trigger_wifi_firmware_crash(self.dut)
+            # Connect DUT to Network
+            wutils.connect_to_wifi_network(self.dut_client, config, check_connectivity=False)
+            # Ping the DUT
+            server_addr = self.dut.droid.connectivityGetIPv4Addresses(
+                    self.ap_iface)[0]
+            asserts.assert_true(
+                utils.adb_shell_ping(
+                        self.dut_client,
+                        count=10,
+                        dest_ip=server_addr,
+                        timeout=20),
+                "%s ping %s failed" % (self.dut_client.serial, server_addr))
+        wutils.stop_wifi_tethering(self.dut)
+
+    @test_tracker_info(uuid="4b7f2d89-82be-41de-9277-e938cc1c318b")
+    def test_firmware_crash_concurrent_reconnect_stress(self):
+        """Firmware crash stress test for concurrent mode
+
+        1. Turn on dut's Wi-Fi and connect to access point
+        2. Turn on dut's hotspot and connected by dut client
+        3. Trigger firmware crash
+        4. Check ssr happened
+        5. Check dut can connect to access point
+        6. Check the connectivity of hotspot's client
+        7. Repeat step 3~6
+        """
+        if self.dut.model not in self.dbs_supported_models:
+            raise signals.TestSkip("%s does not support dual interfaces" % self.dut.model)
+
+        # Connect DUT to Network
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.connect_to_wifi_network(self.dut, self.network)
+        # Setup Soft AP
+        sap_config = wutils.create_softap_config()
+        wutils.start_wifi_tethering(
+            self.dut, sap_config[wutils.WifiEnums.SSID_KEY],
+            sap_config[wutils.WifiEnums.PWD_KEY], wutils.WifiEnums.WIFI_CONFIG_APBAND_2G)
+        config = {
+            "SSID": sap_config[wutils.WifiEnums.SSID_KEY],
+            "password": sap_config[wutils.WifiEnums.PWD_KEY]
+        }
+        # Client connects to Softap
+        wutils.wifi_toggle_state(self.dut_client, True)
+        wutils.connect_to_wifi_network(self.dut_client, config)
+        for count in range(self.stress_count):
+            self.log.info("%s: %d/%d" %
+                (self.current_test_name, count + 1, self.stress_count))
+            wutils.reset_wifi(self.dut_client)
+            wutils.reset_wifi(self.dut)
+            # Trigger firmware crash
+            self.trigger_wifi_firmware_crash(self.dut)
+            wutils.connect_to_wifi_network(self.dut, self.network)
+            wutils.connect_to_wifi_network(self.dut_client, config)
+        wutils.stop_wifi_tethering(self.dut)
diff --git a/acts_tests/tests/google/wifi/WifiCrashTest.py b/acts_tests/tests/google/wifi/WifiCrashTest.py
new file mode 100644
index 0000000..7f2ad68
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiCrashTest.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+# Timeout used for crash recovery.
+RECOVERY_TIMEOUT = 15
+WIFICOND_KILL_SHELL_COMMAND = "killall wificond"
+WIFI_VENDOR_HAL_KILL_SHELL_COMMAND = "killall android.hardware.wifi@1.0-service vendor.google.wifi_ext@1.0-service-vendor"
+SUPPLICANT_KILL_SHELL_COMMAND = "killall wpa_supplicant"
+
+class WifiCrashTest(WifiBaseTest):
+    """Crash Tests for wifi stack.
+
+    Test Bed Requirement:
+    * One Android device
+    * One Wi-Fi network visible to the device.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = []
+        opt_param = ["reference_networks"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True)
+
+        asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+        self.network = self.reference_networks[0]["2g"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+
+    """Helper Functions"""
+
+    """Tests"""
+    @test_tracker_info(uuid="b87fd23f-9bfc-406b-a5b2-17ce6be6c780")
+    def test_wifi_framework_crash_reconnect(self):
+        """Connect to a network, crash framework, then ensure
+        we connect back to the previously connected network.
+
+        Steps:
+        1. Connect to a network.
+        2. Restart framework.
+        3. Reconnect to the previous network.
+
+        """
+        wutils.wifi_connect(self.dut, self.network, num_of_tries=3)
+        # Restart framework
+        self.log.info("Crashing framework")
+        self.dut.restart_runtime()
+        # We won't get the disconnect broadcast because framework crashed.
+        # wutils.wait_for_disconnect(self.dut)
+        time.sleep(RECOVERY_TIMEOUT)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        if wifi_info[WifiEnums.SSID_KEY] != self.network[WifiEnums.SSID_KEY]:
+            raise signals.TestFailure("Device did not connect to the"
+                                      " network after crashing framework.")
+
+    @test_tracker_info(uuid="33f9e4f6-29b8-4116-8f9b-5b13d93b4bcb")
+    def test_wifi_cond_crash_reconnect(self):
+        """Connect to a network, crash wificond, then ensure
+        we connect back to the previously connected network.
+
+        Steps:
+        1. Connect to a network.
+        2. Crash wificond.
+        3. Ensure we get a disconnect.
+        4. Ensure we reconnect to the previous network.
+
+        """
+        wutils.wifi_connect(self.dut, self.network, num_of_tries=3)
+        # Restart wificond
+        self.log.info("Crashing wificond")
+        self.dut.adb.shell(WIFICOND_KILL_SHELL_COMMAND)
+        wutils.wait_for_disconnect(self.dut)
+        time.sleep(RECOVERY_TIMEOUT)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        if wifi_info[WifiEnums.SSID_KEY] != self.network[WifiEnums.SSID_KEY]:
+            raise signals.TestFailure("Device did not connect to the"
+                                      " network after crashing wificond.")
+
+    @test_tracker_info(uuid="463e3d7b-b0b7-4843-b83b-5613a71ae2ac")
+    def test_wifi_vendorhal_crash_reconnect(self):
+        """Connect to a network, crash wifi HAL, then ensure
+        we connect back to the previously connected network.
+
+        Steps:
+        1. Connect to a network.
+        2. Crash wifi HAL.
+        3. Ensure we get a disconnect.
+        4. Ensure we reconnect to the previous network.
+
+        """
+        wutils.wifi_connect(self.dut, self.network, num_of_tries=3)
+        # Restart wificond
+        self.log.info("Crashing wifi HAL")
+        self.dut.adb.shell(WIFI_VENDOR_HAL_KILL_SHELL_COMMAND)
+        wutils.wait_for_disconnect(self.dut)
+        time.sleep(RECOVERY_TIMEOUT)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        if wifi_info[WifiEnums.SSID_KEY] != self.network[WifiEnums.SSID_KEY]:
+            raise signals.TestFailure("Device did not connect to the"
+                                      " network after crashing wifi HAL.")
+
+    @test_tracker_info(uuid="7c5cd1fc-8f8d-494c-beaf-4eb61b48917b")
+    def test_wpa_supplicant_crash_reconnect(self):
+        """Connect to a network, crash wpa_supplicant, then ensure
+        we connect back to the previously connected network.
+
+        Steps:
+        1. Connect to a network.
+        2. Crash wpa_supplicant.
+        3. Ensure we get a disconnect.
+        4. Ensure we reconnect to the previous network.
+
+        """
+        wutils.wifi_connect(self.dut, self.network, num_of_tries=3)
+        # Restart wificond
+        self.log.info("Crashing wpa_supplicant")
+        self.dut.adb.shell(SUPPLICANT_KILL_SHELL_COMMAND)
+        wutils.wait_for_disconnect(self.dut)
+        time.sleep(RECOVERY_TIMEOUT)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        if wifi_info[WifiEnums.SSID_KEY] != self.network[WifiEnums.SSID_KEY]:
+            raise signals.TestFailure("Device did not connect to the"
+                                      " network after crashing wpa_supplicant.")
diff --git a/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py b/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
new file mode 100644
index 0000000..9e5a46f
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+DEFAULT_WAIT_TIME = 2
+
+
+class WifiDiagnosticsTest(WifiBaseTest):
+    """
+    Test Bed Requirement:
+    * One Android device
+    * An open Wi-Fi network.
+    * Verbose logging is on.
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = []
+        opt_param = ["open_network"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_true(
+            len(self.open_network) > 0,
+            "Need at least one open network.")
+        self.open_network = self.open_network[0]["2g"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["open_network"]
+
+    """Tests"""
+
+    @test_tracker_info(uuid="d6f1661b-6732-4939-8c28-f20917774ec0")
+    def test_ringbuffers_are_dumped_during_lsdebug(self):
+        """Steps:
+        1. Connect to a open network.
+        2. Delete old files under data/vendor/tombstones/wifi
+        3. Call lshal debug on wifi hal component
+        4. Verify that files are created under data/vender/tombstones/wifi
+        """
+        wutils.connect_to_wifi_network(self.dut, self.open_network)
+        time.sleep(DEFAULT_WAIT_TIME)
+        self.dut.adb.shell("rm data/vendor/tombstones/wifi/*")
+        try:
+            self.dut.adb.shell("lshal debug android.hardware.wifi@1.2::IWifi")
+        except UnicodeDecodeError:
+            """ Gets this error because adb.shell trys to parse the output to a string
+            but ringbuffer dumps should already be generated """
+            self.log.info("Unicode decode error occurred, but this is ok")
+        file_count_plus_one = self.dut.adb.shell("ls -l data/vendor/tombstones/wifi | wc -l")
+        if int(file_count_plus_one) <= 1:
+            raise signals.TestFailure("Failed to create ringbuffer debug files.")
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiDppTest.py b/acts_tests/tests/google/wifi/WifiDppTest.py
new file mode 100644
index 0000000..64f7f25
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiDppTest.py
@@ -0,0 +1,918 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import binascii
+import queue
+import time
+
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_constants
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+
+class WifiDppTest(WifiBaseTest):
+  """This class tests the DPP API surface.
+
+     Attributes: The tests in this class require one DUT and one helper phone
+     device.
+     The tests in this class do not require a SIM.
+  """
+
+  DPP_TEST_TIMEOUT = 60
+  DPP_TEST_SSID_PREFIX = "dpp_test_ssid_"
+  DPP_TEST_SECURITY_SAE = "SAE"
+  DPP_TEST_SECURITY_PSK_PASSPHRASE = "PSK_PASSPHRASE"
+  DPP_TEST_SECURITY_PSK = "PSK"
+
+  DPP_TEST_EVENT_DPP_CALLBACK = "onDppCallback"
+  DPP_TEST_EVENT_DATA = "data"
+  DPP_TEST_EVENT_ENROLLEE_SUCCESS = "onEnrolleeSuccess"
+  DPP_TEST_EVENT_CONFIGURATOR_SUCCESS = "onConfiguratorSuccess"
+  DPP_TEST_EVENT_PROGRESS = "onProgress"
+  DPP_TEST_EVENT_FAILURE = "onFailure"
+  DPP_TEST_MESSAGE_TYPE = "Type"
+  DPP_TEST_MESSAGE_STATUS = "Status"
+  DPP_TEST_MESSAGE_NETWORK_ID = "NetworkId"
+  DPP_TEST_MESSAGE_FAILURE_SSID = "onFailureSsid"
+  DPP_TEST_MESSAGE_FAILURE_CHANNEL_LIST = "onFailureChannelList"
+  DPP_TEST_MESSAGE_FAILURE_BAND_LIST = "onFailureBandList"
+
+  DPP_TEST_NETWORK_ROLE_STA = "sta"
+  DPP_TEST_NETWORK_ROLE_AP = "ap"
+
+  DPP_TEST_PARAM_SSID = "SSID"
+  DPP_TEST_PARAM_PASSWORD = "Password"
+
+  WPA_SUPPLICANT_SECURITY_SAE = "sae"
+  WPA_SUPPLICANT_SECURITY_PSK = "psk"
+
+  DPP_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0
+  DPP_EVENT_PROGRESS_RESPONSE_PENDING = 1
+  DPP_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2
+  DPP_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3
+
+  DPP_EVENT_SUCCESS_CONFIGURATION_SENT = 0
+  DPP_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1
+
+  def setup_class(self):
+    """ Sets up the required dependencies from the config file and configures the device for
+        WifiService API tests.
+
+        Returns:
+          True is successfully configured the requirements for testing.
+    """
+    super().setup_class()
+    # Device 0 is under test. Device 1 performs the responder role
+    self.dut = self.android_devices[0]
+    self.helper_dev = self.android_devices[1]
+
+    # Do a simple version of init - mainly just sync the time and enable
+    # verbose logging.  We would also like to test with phones in less
+    # constrained states (or add variations where we specifically
+    # constrain).
+    utils.require_sl4a((self.dut,))
+    utils.sync_device_time(self.dut)
+
+    req_params = ["dpp_r1_test_only"]
+    opt_param = ["wifi_psk_network", "wifi_sae_network"]
+    self.unpack_userparams(
+      req_param_names=req_params, opt_param_names=opt_param)
+
+    self.dut.log.info(
+      "Parsed configs: %s %s" % (self.wifi_psk_network, self.wifi_sae_network))
+
+    # Set up the networks. This is optional. In case these networks are not initialized,
+    # the script will create random ones. However, a real AP is required to pass DPP R2 test.
+    # Most efficient setup would be to use an AP in WPA2/WPA3 transition mode.
+    if self.DPP_TEST_PARAM_SSID in self.wifi_psk_network:
+      self.psk_network_ssid = self.wifi_psk_network[self.DPP_TEST_PARAM_SSID]
+    else:
+      self.psk_network_ssid = None
+
+    if self.DPP_TEST_PARAM_PASSWORD in self.wifi_psk_network:
+      self.psk_network_password = self.wifi_psk_network[self.DPP_TEST_PARAM_PASSWORD]
+    else:
+      self.psk_network_ssid = None
+
+    if self.DPP_TEST_PARAM_SSID in self.wifi_sae_network:
+      self.sae_network_ssid = self.wifi_sae_network[self.DPP_TEST_PARAM_SSID]
+    else:
+      self.sae_network_ssid = None
+
+    if self.DPP_TEST_PARAM_PASSWORD in self.wifi_sae_network:
+      self.sae_network_password = self.wifi_sae_network[self.DPP_TEST_PARAM_PASSWORD]
+    else:
+      self.sae_network_ssid = None
+
+    if self.dpp_r1_test_only == "False":
+      if not self.wifi_psk_network or not self.wifi_sae_network:
+        asserts.fail("Must specify wifi_psk_network and wifi_sae_network for DPP R2 tests")
+
+    # Enable verbose logging on the dut
+    self.dut.droid.wifiEnableVerboseLogging(1)
+    asserts.assert_true(self.dut.droid.wifiGetVerboseLoggingLevel() == 1,
+                        "Failed to enable WiFi verbose logging on the dut.")
+
+  def teardown_class(self):
+    wutils.reset_wifi(self.dut)
+
+  def create_and_save_wifi_network_config(self, security, random_network=False,
+                                          r2_auth_error=False):
+    """ Create a config with random SSID and password.
+
+            Args:
+               security: Security type: PSK or SAE
+               random_network: A boolean that indicates if to create a random network
+               r2_auth_error: A boolean that indicates if to create a network with a bad password
+
+            Returns:
+               A tuple with the config and networkId for the newly created and
+               saved network.
+    """
+    if security == self.DPP_TEST_SECURITY_PSK:
+      if self.psk_network_ssid is None or self.psk_network_password is None or \
+              random_network is True:
+        config_ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+        config_password = utils.rand_ascii_str(8)
+      else:
+        config_ssid = self.psk_network_ssid
+        if r2_auth_error:
+          config_password = utils.rand_ascii_str(8)
+        else:
+          config_password = self.psk_network_password
+    else:
+      if self.sae_network_ssid is None or self.sae_network_password is None or \
+              random_network is True:
+        config_ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+        config_password = utils.rand_ascii_str(8)
+      else:
+        config_ssid = self.sae_network_ssid
+        if r2_auth_error:
+          config_password = utils.rand_ascii_str(8)
+        else:
+          config_password = self.sae_network_password
+
+    self.dut.log.info(
+        "creating config: %s %s %s" % (config_ssid, config_password, security))
+    config = {
+        wutils.WifiEnums.SSID_KEY: config_ssid,
+        wutils.WifiEnums.PWD_KEY: config_password,
+        wutils.WifiEnums.SECURITY: security
+    }
+
+    # Now save the config.
+    network_id = self.dut.droid.wifiAddNetwork(config)
+    self.dut.log.info("saved config: network_id = %d" % network_id)
+    return network_id
+
+  def check_network_config_saved(self, expected_ssid, security, network_id):
+    """ Get the configured networks and check if the provided network ID is present.
+
+            Args:
+             expected_ssid: Expected SSID to match with received configuration.
+             security: Security type to match, PSK or SAE
+
+            Returns:
+                True if the WifiConfig is present.
+    """
+    networks = self.dut.droid.wifiGetConfiguredNetworks()
+    if not networks:
+      return False
+
+    # Normalize PSK and PSK Passphrase to PSK
+    if security == self.DPP_TEST_SECURITY_PSK_PASSPHRASE:
+      security = self.DPP_TEST_SECURITY_PSK
+
+    # If the device doesn't support SAE, then the test fallbacks to PSK
+    if not self.dut.droid.wifiIsWpa3SaeSupported() and \
+              security == self.DPP_TEST_SECURITY_SAE:
+      security = self.DPP_TEST_SECURITY_PSK
+
+    for network in networks:
+      if network_id == network['networkId'] and \
+              security == network[wutils.WifiEnums.SECURITY] and \
+              expected_ssid == network[wutils.WifiEnums.SSID_KEY]:
+        self.log.info("Found SSID %s" % network[wutils.WifiEnums.SSID_KEY])
+        return True
+    return False
+
+  def forget_network(self, network_id):
+    """ Simple method to call wifiForgetNetwork and wait for confirmation callback.
+
+            Returns:
+                True if network was successfully deleted.
+        """
+    self.dut.log.info("Deleting config: networkId = %s" % network_id)
+    self.dut.droid.wifiForgetNetwork(network_id)
+    try:
+      event = self.dut.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS, 10)
+      return True
+    except queue.Empty:
+      self.dut.log.error("Failed to forget network")
+      return False
+
+  def gen_uri(self, device, info="DPP_TESTER", chan="81/1", mac=None):
+    """Generate a URI on a device
+
+            Args:
+                device: Device object
+                mac: MAC address to use
+                info: Optional info to be embedded in URI
+                chan: Optional channel info
+
+            Returns:
+             URI ID to be used later
+    """
+
+    # Clean up any previous URIs
+    self.del_uri(device, "'*'")
+
+    self.log.info("Generating a URI for the Responder")
+    cmd = "wpa_cli DPP_BOOTSTRAP_GEN type=qrcode info=%s" % info
+
+    if mac:
+      cmd += " mac=%s" % mac
+
+    if chan:
+      cmd += " chan=%s" % chan
+
+    result = device.adb.shell(cmd)
+
+    if "FAIL" in result:
+      asserts.fail("gen_uri: Failed to generate a URI. Command used: %s" % cmd)
+
+    if "\n" not in result:
+      asserts.fail(
+          "gen_uri: Helper device not responding correctly, "
+          "may need to restart it. Command used: %s" % cmd)
+
+    result = result[result.index("\n") + 1:]
+    device.log.info("Generated URI, id = %s" % result)
+
+    return result
+
+  def get_uri(self, device, uri_id):
+    """Get a previously generated URI from a device
+
+            Args:
+                device: Device object
+                uri_id: URI ID returned by gen_uri method
+
+            Returns:
+                URI string
+
+        """
+    self.log.info("Reading the contents of the URI of the Responder")
+    cmd = "wpa_cli DPP_BOOTSTRAP_GET_URI %s" % uri_id
+    result = device.adb.shell(cmd)
+
+    if "FAIL" in result:
+      asserts.fail("get_uri: Failed to read URI. Command used: %s" % cmd)
+
+    result = result[result.index("\n") + 1:]
+    device.log.info("URI contents = %s" % result)
+
+    return result
+
+  def del_uri(self, device, uri_id):
+    """Delete a previously generated URI
+
+          Args:
+          device: Device object
+          uri_id: URI ID returned by gen_uri method
+    """
+    self.log.info("Deleting the Responder URI")
+    cmd = "wpa_cli DPP_BOOTSTRAP_REMOVE %s" % uri_id
+    result = device.adb.shell(cmd)
+
+    # If URI was already flushed, ignore a failure here
+    if "FAIL" not in result:
+      device.log.info("Deleted URI, id = %s" % uri_id)
+
+  def start_responder_configurator(self,
+                                   device,
+                                   freq=2412,
+                                   net_role=DPP_TEST_NETWORK_ROLE_STA,
+                                   security=DPP_TEST_SECURITY_SAE,
+                                   invalid_config=False):
+    """Start a responder on helper device
+
+           Args:
+               device: Device object
+               freq: Frequency to listen on
+               net_role: Network role to configure
+               security: Security type: SAE or PSK
+               invalid_config: Send invalid configuration (negative test)
+
+            Returns:
+                ssid: SSID name of the network to be configured
+
+        """
+    if not net_role or (net_role != self.DPP_TEST_NETWORK_ROLE_STA and
+                        net_role != self.DPP_TEST_NETWORK_ROLE_AP):
+      asserts.fail("start_responder: Must specify net_role sta or ap")
+
+    self.log.info("Starting Responder in Configurator mode, frequency %sMHz" % freq)
+
+    conf = "conf=%s-" % net_role
+
+    use_psk = False
+
+    if security == self.DPP_TEST_SECURITY_SAE:
+      if not self.dut.droid.wifiIsWpa3SaeSupported():
+        self.log.warning("SAE not supported on device! reverting to PSK")
+        security = self.DPP_TEST_SECURITY_PSK_PASSPHRASE
+
+    ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+    password = utils.rand_ascii_str(8)
+
+    if security == self.DPP_TEST_SECURITY_SAE:
+      conf += self.WPA_SUPPLICANT_SECURITY_SAE
+      if not self.sae_network_ssid is None:
+        ssid = self.sae_network_ssid
+        password = self.sae_network_password
+    elif security == self.DPP_TEST_SECURITY_PSK_PASSPHRASE:
+      conf += self.WPA_SUPPLICANT_SECURITY_PSK
+      if not self.psk_network_ssid is None:
+        ssid = self.psk_network_ssid
+        password = self.psk_network_password
+    else:
+      conf += self.WPA_SUPPLICANT_SECURITY_PSK
+      use_psk = True
+
+    self.log.debug("SSID = %s" % ssid)
+
+    ssid_encoded = binascii.hexlify(ssid.encode()).decode()
+
+    if use_psk:
+      psk = utils.rand_ascii_str(16)
+      if not invalid_config:
+        psk_encoded = binascii.b2a_hex(psk.encode()).decode()
+      else:
+        # Use the psk as is without hex encoding, will make it invalid
+        psk_encoded = psk
+      self.log.debug("PSK = %s" % psk)
+    else:
+      if not invalid_config:
+        password_encoded = binascii.b2a_hex(password.encode()).decode()
+      else:
+        # Use the password as is without hex encoding, will make it invalid
+        password_encoded = password
+      self.log.debug("Password = %s" % password)
+
+    conf += " ssid=%s" % ssid_encoded
+
+    if password:  # SAE password or PSK passphrase
+      conf += " pass=%s" % password_encoded
+    else:  # PSK
+      conf += " psk=%s" % psk_encoded
+
+    # Stop responder first
+    self.stop_responder(device)
+
+    cmd = "wpa_cli set dpp_configurator_params guard=1 %s" % conf
+    device.log.debug("Command used: %s" % cmd)
+    result = self.helper_dev.adb.shell(cmd)
+    if "FAIL" in result:
+      asserts.fail(
+          "start_responder_configurator: Failure. Command used: %s" % cmd)
+
+    cmd = "wpa_cli DPP_LISTEN %d role=configurator netrole=%s" % (freq,
+                                                                  net_role)
+    device.log.debug("Command used: %s" % cmd)
+    result = self.helper_dev.adb.shell(cmd)
+    if "FAIL" in result:
+      asserts.fail(
+          "start_responder_configurator: Failure. Command used: %s" % cmd)
+
+    device.log.info("Started responder in configurator mode")
+    return ssid
+
+  def start_responder_enrollee(self,
+                               device,
+                               freq=2412,
+                               net_role=DPP_TEST_NETWORK_ROLE_STA):
+    """Start a responder-enrollee on helper device
+
+           Args:
+               device: Device object
+               freq: Frequency to listen on
+               net_role: Network role to request
+
+            Returns:
+                ssid: SSID name of the network to be configured
+
+        """
+    if not net_role or (net_role != self.DPP_TEST_NETWORK_ROLE_STA and
+                        net_role != self.DPP_TEST_NETWORK_ROLE_AP):
+      asserts.fail("start_responder: Must specify net_role sta or ap")
+
+    # Stop responder first
+    self.stop_responder(device)
+    self.log.info("Starting Responder in Enrollee mode, frequency %sMHz" % freq)
+
+    cmd = "wpa_cli DPP_LISTEN %d role=enrollee netrole=%s" % (freq, net_role)
+    result = device.adb.shell(cmd)
+
+    if "FAIL" in result:
+      asserts.fail("start_responder_enrollee: Failure. Command used: %s" % cmd)
+
+    device.adb.shell("wpa_cli set dpp_config_processing 2")
+
+    device.log.info("Started responder in enrollee mode")
+
+  def stop_responder(self, device, flush=False):
+    """Stop responder on helper device
+
+       Args:
+           device: Device object
+    """
+    result = device.adb.shell("wpa_cli DPP_STOP_LISTEN")
+    if "FAIL" in result:
+      asserts.fail("stop_responder: Failed to stop responder")
+    device.adb.shell("wpa_cli set dpp_configurator_params")
+    device.adb.shell("wpa_cli set dpp_config_processing 0")
+    if flush:
+      device.adb.shell("wpa_cli flush")
+    device.log.info("Stopped responder")
+
+  def start_dpp_as_initiator_configurator(self,
+                                          security,
+                                          use_mac,
+                                          responder_chan="81/1",
+                                          responder_freq=2412,
+                                          net_role=DPP_TEST_NETWORK_ROLE_STA,
+                                          cause_timeout=False,
+                                          fail_authentication=False,
+                                          invalid_uri=False,
+                                          r2_no_ap=False,
+                                          r2_auth_error=False):
+    """ Test Easy Connect (DPP) as initiator configurator.
+
+                1. Enable wifi, if needed
+                2. Create and save a random config.
+                3. Generate a URI using the helper device
+                4. Start DPP as responder-enrollee on helper device
+                5. Start DPP as initiator configurator on dut
+                6. Check if configurator sent successfully
+                7. Delete the URI from helper device
+                8. Remove the config.
+
+        Args:
+            security: Security type, a string "SAE" or "PSK"
+            use_mac: A boolean indicating whether to use the device's MAC
+              address (if True) or use a Broadcast (if False).
+            responder_chan: Responder channel to specify in the URI
+            responder_freq: Frequency that the Responder would actually listen on.
+              Note: To succeed, there must be a correlation between responder_chan, which is what
+              the URI advertises, and responder_freq which is the actual frequency. See:
+              https://en.wikipedia.org/wiki/List_of_WLAN_channels
+            net_role: Network role, a string "sta" or "ap"
+            cause_timeout: Intentionally don't start the responder to cause a
+              timeout
+            fail_authentication: Fail authentication by corrupting the
+              responder's key
+            invalid_uri: Use garbage string instead of a URI
+            r2_no_ap: Indicates if to test DPP R2 no AP failure event
+            r2_auth_error: Indicates if to test DPP R2 authentication failure
+    """
+    if not self.dut.droid.wifiIsEasyConnectSupported():
+      self.log.warning("Easy Connect is not supported on device!")
+      return
+
+    wutils.wifi_toggle_state(self.dut, True)
+    test_network_id = self.create_and_save_wifi_network_config(security, random_network=r2_no_ap,
+                                                               r2_auth_error=r2_auth_error)
+
+    if use_mac:
+      mac = autils.get_mac_addr(self.helper_dev, "wlan0")
+    else:
+      mac = None
+
+    if invalid_uri:
+      enrollee_uri = "dskjgnkdjfgnkdsjfgnsDFGDIFGKDSJFGFDbgjdsnbkjdfnkbgsdfgFDSGSDfgesouhgureho" \
+                     "iu3ht98368903434089ut4958763094u0934ujg094j5oifegjfds"
+    else:
+      # Generate a URI with default info and channel
+      uri_id = self.gen_uri(self.helper_dev, chan=responder_chan, mac=mac)
+
+      # Get the URI. This is equal to scanning a QR code
+      enrollee_uri = self.get_uri(self.helper_dev, uri_id)
+
+      # Corrupt the responder key if required
+      if fail_authentication:
+        enrollee_uri = enrollee_uri[:80] + "DeAdBeeF" + enrollee_uri[88:]
+        self.log.info("Corrupted enrollee URI: %s" % enrollee_uri)
+
+    if not cause_timeout:
+      # Start DPP as an enrolle-responder for STA on helper device
+      self.start_responder_enrollee(self.helper_dev, freq=responder_freq, net_role=net_role)
+    else:
+      self.log.info("Not starting DPP responder on purpose")
+
+    self.log.info("Starting DPP in Configurator-Initiator mode")
+
+    # Start DPP as configurator-initiator on dut
+    self.dut.droid.startEasyConnectAsConfiguratorInitiator(enrollee_uri,
+                                                   test_network_id, net_role)
+
+    start_time = time.time()
+    while time.time() < start_time + self.DPP_TEST_TIMEOUT:
+      dut_event = self.dut.ed.pop_event(self.DPP_TEST_EVENT_DPP_CALLBACK,
+                                        self.DPP_TEST_TIMEOUT)
+      if dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_TYPE] \
+              == self.DPP_TEST_EVENT_ENROLLEE_SUCCESS:
+        asserts.fail("DPP failure, unexpected result!")
+        break
+      if dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_TYPE] \
+              == self.DPP_TEST_EVENT_CONFIGURATOR_SUCCESS:
+        if cause_timeout or fail_authentication or invalid_uri or r2_no_ap or r2_auth_error:
+          asserts.fail(
+              "Unexpected DPP success, status code: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+        else:
+          val = dut_event[self.DPP_TEST_EVENT_DATA][
+              self.DPP_TEST_MESSAGE_STATUS]
+          if val == self.DPP_EVENT_SUCCESS_CONFIGURATION_SENT:
+            self.dut.log.info("DPP Configuration sent success")
+          if val == self.DPP_EVENT_SUCCESS_CONFIGURATION_APPLIED:
+            self.dut.log.info("DPP Configuration applied by enrollee")
+        break
+      if dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_TYPE] \
+              == self.DPP_TEST_EVENT_PROGRESS:
+        self.dut.log.info("DPP progress event")
+        val = dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS]
+        if val == self.DPP_EVENT_PROGRESS_AUTHENTICATION_SUCCESS:
+          self.dut.log.info("DPP Authentication success")
+        elif val == self.DPP_EVENT_PROGRESS_RESPONSE_PENDING:
+          self.dut.log.info("DPP Response pending")
+        elif val == self.DPP_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE:
+          self.dut.log.info("DPP Configuration sent, waiting response")
+        elif val == self.DPP_EVENT_PROGRESS_CONFIGURATION_ACCEPTED:
+          self.dut.log.info("Configuration accepted")
+        continue
+      if dut_event[self.DPP_TEST_EVENT_DATA][
+          self.DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_FAILURE:
+        if cause_timeout or fail_authentication or invalid_uri or r2_no_ap or r2_auth_error:
+          self.dut.log.info(
+              "Error %s occurred, as expected" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+          if r2_no_ap or r2_auth_error:
+            if not dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_SSID]:
+              asserts.fail("Expected SSID value in DPP R2 onFailure event")
+            self.dut.log.info(
+              "Enrollee searched for SSID %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_SSID])
+          if r2_no_ap:
+            if not dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_CHANNEL_LIST]:
+              asserts.fail("Expected Channel list value in DPP R2 onFailure event")
+            self.dut.log.info(
+              "Enrollee scanned the following channels: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_CHANNEL_LIST])
+          if r2_no_ap or r2_auth_error:
+            if not dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_BAND_LIST]:
+              asserts.fail("Expected Band Support list value in DPP R2 onFailure event")
+            self.dut.log.info(
+              "Enrollee supports the following bands: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_BAND_LIST])
+        else:
+          asserts.fail(
+              "DPP failure, status code: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+        break
+
+    # Clear all pending events.
+    self.dut.ed.clear_all_events()
+
+    # Stop responder
+    self.stop_responder(self.helper_dev, flush=True)
+
+    if not invalid_uri:
+      # Delete URI
+      self.del_uri(self.helper_dev, uri_id)
+
+    asserts.assert_true(
+        self.forget_network(test_network_id),
+        "Test network not deleted from configured networks.")
+
+  def start_dpp_as_initiator_enrollee(self,
+                                      security,
+                                      use_mac,
+                                      cause_timeout=False,
+                                      invalid_config=False):
+    """ Test Easy Connect (DPP) as initiator enrollee.
+
+                1. Enable wifi, if needed
+                2. Start DPP as responder-configurator on helper device
+                3. Start DPP as initiator enrollee on dut
+                4. Check if configuration received successfully
+                5. Delete the URI from helper device
+                6. Remove the config.
+
+        Args:
+            security: Security type, a string "SAE" or "PSK"
+            use_mac: A boolean indicating whether to use the device's MAC
+              address (if True) or use a Broadcast (if False).
+            cause_timeout: Intentionally don't start the responder to cause a
+              timeout
+            invalid_config: Responder to intentionally send malformed
+              configuration
+    """
+    if not self.dut.droid.wifiIsEasyConnectSupported():
+      self.log.warning("Easy Connect is not supported on device!")
+      return
+
+    wutils.wifi_toggle_state(self.dut, True)
+
+    if use_mac:
+      mac = autils.get_mac_addr(self.helper_dev, "wlan0")
+    else:
+      mac = None
+
+    # Generate a URI with default info and channel
+    uri_id = self.gen_uri(self.helper_dev, mac=mac)
+
+    # Get the URI. This is equal to scanning a QR code
+    configurator_uri = self.get_uri(self.helper_dev, uri_id)
+
+    if not cause_timeout:
+      # Start DPP as an configurator-responder for STA on helper device
+      ssid = self.start_responder_configurator(
+          self.helper_dev, security=security, invalid_config=invalid_config)
+    else:
+      self.log.info(
+          "Not starting a responder configurator on helper device, on purpose")
+      ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+
+    self.log.info("Starting DPP in Enrollee-Initiator mode")
+
+    # Start DPP as enrollee-initiator on dut
+    self.dut.droid.startEasyConnectAsEnrolleeInitiator(configurator_uri)
+
+    network_id = 0
+
+    start_time = time.time()
+    while time.time() < start_time + self.DPP_TEST_TIMEOUT:
+      dut_event = self.dut.ed.pop_event(self.DPP_TEST_EVENT_DPP_CALLBACK,
+                                        self.DPP_TEST_TIMEOUT)
+      if dut_event[self.DPP_TEST_EVENT_DATA][
+          self.DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_ENROLLEE_SUCCESS:
+        if cause_timeout or invalid_config:
+          asserts.fail(
+              "Unexpected DPP success, status code: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+        else:
+          self.dut.log.info("DPP Configuration received success")
+          network_id = dut_event[self.DPP_TEST_EVENT_DATA][
+              self.DPP_TEST_MESSAGE_NETWORK_ID]
+          self.dut.log.info("NetworkID: %d" % network_id)
+        break
+      if dut_event[self.DPP_TEST_EVENT_DATA][
+          self
+          .DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_CONFIGURATOR_SUCCESS:
+        asserts.fail(
+            "DPP failure, unexpected result: %s" %
+            dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+        break
+      if dut_event[self.DPP_TEST_EVENT_DATA][
+          self.DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_PROGRESS:
+        self.dut.log.info("DPP progress event")
+        val = dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS]
+        if val == 0:
+          self.dut.log.info("DPP Authentication success")
+        elif val == 1:
+          self.dut.log.info("DPP Response pending")
+        continue
+      if dut_event[self.DPP_TEST_EVENT_DATA][
+          self.DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_FAILURE:
+        if cause_timeout or invalid_config:
+          self.dut.log.info(
+              "Error %s occurred, as expected" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+        else:
+          asserts.fail(
+              "DPP failure, status code: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+        break
+      asserts.fail("Unknown message received")
+
+    # Clear all pending events.
+    self.dut.ed.clear_all_events()
+
+    # Stop responder
+    self.stop_responder(self.helper_dev, flush=True)
+
+    # Delete URI
+    self.del_uri(self.helper_dev, uri_id)
+
+    if not (invalid_config or cause_timeout):
+      # Check that the saved network is what we expect
+      asserts.assert_true(
+          self.check_network_config_saved(ssid, security, network_id),
+          "Could not find the expected network: %s" % ssid)
+
+      asserts.assert_true(
+          self.forget_network(network_id),
+          "Test network not deleted from configured networks.")
+
+  """ Tests Begin """
+
+  @test_tracker_info(uuid="30893d51-2069-4e1c-8917-c8a840f91b59")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_5G(self):
+    asserts.skip_if(not self.dut.droid.wifiIs5GHzBandSupported() or
+            not self.helper_dev.droid.wifiIs5GHzBandSupported(),
+            "5G not supported on at least on test device")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, responder_chan="126/149", responder_freq=5745,
+      use_mac=True)
+
+  @test_tracker_info(uuid="54d1d19a-aece-459c-b819-9d4b1ae63f77")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_5G_broadcast(self):
+    asserts.skip_if(not self.dut.droid.wifiIs5GHzBandSupported() or
+                    not self.helper_dev.droid.wifiIs5GHzBandSupported(),
+                    "5G not supported on at least on test device")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, responder_chan="126/149", responder_freq=5745,
+      use_mac=False)
+
+  @test_tracker_info(uuid="18270a69-300c-4f54-87fd-c19073a2854e ")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_no_chan_in_uri_listen_on_5745_broadcast(self):
+    asserts.skip_if(not self.dut.droid.wifiIs5GHzBandSupported() or
+                    not self.helper_dev.droid.wifiIs5GHzBandSupported(),
+                    "5G not supported on at least on test device")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, responder_chan=None, responder_freq=5745, use_mac=False)
+
+  @test_tracker_info(uuid="fbdd687c-954a-400b-9da3-2d17e28b0798")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_no_chan_in_uri_listen_on_5745(self):
+    asserts.skip_if(not self.dut.droid.wifiIs5GHzBandSupported() or
+                    not self.helper_dev.droid.wifiIs5GHzBandSupported(),
+                    "5G not supported on at least on test device")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, responder_chan=None, responder_freq=5745, use_mac=True)
+
+  @test_tracker_info(uuid="570f499f-ab12-4405-af14-c9ed36da2e01")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_no_chan_in_uri_listen_on_2462_broadcast(self):
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, responder_chan=None, responder_freq=2462, use_mac=False)
+
+  @test_tracker_info(uuid="e1f083e0-0878-4c49-8ac5-d7c6bba24625")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_no_chan_in_uri_listen_on_2462(self):
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, responder_chan=None, responder_freq=2462, use_mac=True)
+
+  @test_tracker_info(uuid="d2a526f5-4269-493d-bd79-4e6d1b7b00f0")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK, use_mac=True)
+
+  @test_tracker_info(uuid="6ead218c-222b-45b8-8aad-fe7d883ed631")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_sae(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_SAE, use_mac=True)
+
+  @test_tracker_info(uuid="1686adb5-1b3c-4e6d-a969-6b007bdd990d")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_passphrase(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE, use_mac=True)
+
+  @test_tracker_info(uuid="3958feb5-1a0c-4487-9741-ac06f04c55a2")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_sae_broadcast(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_SAE, use_mac=False)
+
+  @test_tracker_info(uuid="fe6d66f5-73a1-46e9-8f49-73b8f332cc8c")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_passphrase_broadcast(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE, use_mac=False)
+
+  @test_tracker_info(uuid="9edd372d-e2f1-4545-8d04-6a1636fcbc4b")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_sae_for_ap(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_SAE,
+        use_mac=True,
+        net_role=self.DPP_TEST_NETWORK_ROLE_AP)
+
+  @test_tracker_info(uuid="e9eec912-d665-4926-beac-859cb13dc17b")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_with_psk_passphrase_for_ap(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=True,
+        net_role=self.DPP_TEST_NETWORK_ROLE_AP)
+
+  @test_tracker_info(uuid="8055694f-606f-41dd-9826-3ea1e9b007f8")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_enrollee_with_sae(self):
+    self.start_dpp_as_initiator_enrollee(
+        security=self.DPP_TEST_SECURITY_SAE, use_mac=True)
+
+  @test_tracker_info(uuid="c1e9f605-b5c0-4e53-8a08-1b0087a667fa")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_enrollee_with_psk_passphrase(self):
+    self.start_dpp_as_initiator_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE, use_mac=True)
+
+  @test_tracker_info(uuid="1d7f30ad-2f9a-427a-8059-651dc8827ae2")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_enrollee_with_sae_broadcast(self):
+    self.start_dpp_as_initiator_enrollee(
+        security=self.DPP_TEST_SECURITY_SAE, use_mac=False)
+
+  @test_tracker_info(uuid="0cfc2645-600e-4f2b-ab5c-fcee6d363a9a")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_enrollee_with_psk_passphrase_broadcast(self):
+    self.start_dpp_as_initiator_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE, use_mac=False)
+
+  @test_tracker_info(uuid="2e26b248-65dd-41f6-977b-e223d72b2de9")
+  @WifiBaseTest.wifi_test_wrap
+  def test_start_dpp_as_initiator_enrollee_receive_invalid_config(self):
+    self.start_dpp_as_initiator_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=True,
+        invalid_config=True)
+
+  @test_tracker_info(uuid="ed189661-d1c1-4626-9f01-3b7bb8a417fe")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_fail_authentication(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=True,
+        fail_authentication=True)
+
+  @test_tracker_info(uuid="5a8c6587-fbb4-4a27-9cba-af6f8935833a")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_fail_unicast_timeout(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=True,
+        cause_timeout=True)
+
+  @test_tracker_info(uuid="b12353ac-1a04-4036-81a4-2d2d0c653dbb")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_fail_broadcast_timeout(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=False,
+        cause_timeout=True)
+
+  @test_tracker_info(uuid="eeff91be-09ce-4a33-8b4f-ece40eb51c76")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_initiator_configurator_invalid_uri(self):
+    self.start_dpp_as_initiator_configurator(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=True,
+        invalid_uri=True)
+
+  @test_tracker_info(uuid="1fa25f58-0d0e-40bd-8714-ab78957514d9")
+  @WifiBaseTest.wifi_test_wrap
+  def test_start_dpp_as_initiator_enrollee_fail_timeout(self):
+    self.start_dpp_as_initiator_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        use_mac=True,
+        cause_timeout=True)
+
+  @test_tracker_info(uuid="23601af8-118e-4ba8-89e3-5da2e37bbd7d")
+  def test_dpp_as_initiator_configurator_fail_r2_no_ap(self):
+    asserts.skip_if(self.dpp_r1_test_only == "True",
+                    "DPP R1 test, skipping this test for DPP R2 only")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, use_mac=True, r2_no_ap=True)
+
+  @test_tracker_info(uuid="7f9756d3-f28f-498e-8dcf-ac3816303998")
+  def test_dpp_as_initiator_configurator_fail_r2_auth_error(self):
+    asserts.skip_if(self.dpp_r1_test_only == "True",
+                    "DPP R1 test, skipping this test for DPP R2 only")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, use_mac=True, r2_auth_error=True)
+
+""" Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py b/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
new file mode 100644
index 0000000..05b3549
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
@@ -0,0 +1,254 @@
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import pprint
+import random
+import time
+
+from acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+# EAP Macros
+EAP = WifiEnums.Eap
+EapPhase2 = WifiEnums.EapPhase2
+
+# Enterprise Config Macros
+Ent = WifiEnums.Enterprise
+
+
+class WifiEnterpriseRoamingTest(WifiBaseTest):
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = (
+            "attn_vals",
+            # Expected time within which roaming should finish, in seconds.
+            "roam_interval",
+            "ca_cert",
+            "client_cert",
+            "client_key",
+            "eap_identity",
+            "eap_password",
+            "device_password",
+            "radius_conf_2g",
+            "radius_conf_5g")
+        self.unpack_userparams(req_params)
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(
+                mirror_ap=True,
+                ent_network=True,
+                ap_count=2,
+                radius_conf_2g=self.radius_conf_2g,
+                radius_conf_5g=self.radius_conf_5g,)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(
+                mirror_ap=True,
+                ent_network=True,
+                ap_count=2,
+                radius_conf_2g=self.radius_conf_2g,
+                radius_conf_5g=self.radius_conf_5g,)
+        self.ent_network_2g_a = self.ent_networks[0]["2g"]
+        self.ent_network_2g_b = self.ent_networks[1]["2g"]
+        self.ent_roaming_ssid = self.ent_network_2g_a[WifiEnums.SSID_KEY]
+        if "AccessPoint" in self.user_params:
+            self.bssid_a = self.ent_network_2g_a[WifiEnums.BSSID_KEY.lower()]
+            self.bssid_b = self.ent_network_2g_b[WifiEnums.BSSID_KEY.lower()]
+        elif "OpenWrtAP" in self.user_params:
+            self.bssid_a = self.bssid_map[0]["2g"][self.ent_roaming_ssid]
+            self.bssid_b = self.bssid_map[1]["2g"][self.ent_roaming_ssid]
+
+        self.config_peap = {
+            Ent.EAP: int(EAP.PEAP),
+            Ent.CA_CERT: self.ca_cert,
+            Ent.IDENTITY: self.eap_identity,
+            Ent.PASSWORD: self.eap_password,
+            Ent.PHASE2: int(EapPhase2.MSCHAPV2),
+            WifiEnums.SSID_KEY: self.ent_roaming_ssid
+        }
+        self.config_tls = {
+            Ent.EAP: int(EAP.TLS),
+            Ent.CA_CERT: self.ca_cert,
+            WifiEnums.SSID_KEY: self.ent_roaming_ssid,
+            Ent.CLIENT_CERT: self.client_cert,
+            Ent.PRIVATE_KEY_ID: self.client_key,
+            Ent.IDENTITY: self.eap_identity,
+        }
+        self.config_ttls = {
+            Ent.EAP: int(EAP.TTLS),
+            Ent.CA_CERT: self.ca_cert,
+            Ent.IDENTITY: self.eap_identity,
+            Ent.PASSWORD: self.eap_password,
+            Ent.PHASE2: int(EapPhase2.MSCHAPV2),
+            WifiEnums.SSID_KEY: self.ent_roaming_ssid
+        }
+        self.config_sim = {
+            Ent.EAP: int(EAP.SIM),
+            WifiEnums.SSID_KEY: self.ent_roaming_ssid,
+        }
+        self.attn_a = self.attenuators[0]
+        self.attn_b = self.attenuators[2]
+        if "OpenWrtAP" in self.user_params:
+            self.attn_b = self.attenuators[1]
+        # Set screen lock password so ConfigStore is unlocked.
+        self.dut.droid.setDevicePassword(self.device_password)
+        self.set_attns("default")
+
+    def teardown_class(self):
+        wutils.reset_wifi(self.dut)
+        self.dut.droid.disableDevicePassword(self.device_password)
+        self.dut.ed.clear_all_events()
+        self.set_attns("default")
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wifiStartTrackingStateChange()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.reset_wifi(self.dut)
+        self.dut.ed.clear_all_events()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        self.dut.droid.wifiStopTrackingStateChange()
+        self.set_attns("default")
+
+    def set_attns(self, attn_val_name):
+        """Sets attenuation values on attenuators used in this test.
+
+        Args:
+            attn_val_name: Name of the attenuation value pair to use.
+        """
+        self.log.info("Set attenuation values to %s",
+                      self.attn_vals[attn_val_name])
+        try:
+            self.attn_a.set_atten(self.attn_vals[attn_val_name][0])
+            self.attn_b.set_atten(self.attn_vals[attn_val_name][1])
+        except:
+            self.log.exception("Failed to set attenuation values %s.",
+                           attn_val_name)
+            raise
+
+    def trigger_roaming_and_validate(self, attn_val_name, expected_con):
+        """Sets attenuators to trigger roaming and validate the DUT connected
+        to the BSSID expected.
+
+        Args:
+            attn_val_name: Name of the attenuation value pair to use.
+            expected_con: The expected info of the network to we expect the DUT
+                to roam to.
+        """
+        self.set_attns(attn_val_name)
+        self.log.info("Wait %ss for roaming to finish.", self.roam_interval)
+        time.sleep(self.roam_interval)
+        try:
+            self.dut.droid.wakeLockAcquireBright()
+            self.dut.droid.wakeUpNow()
+            wutils.verify_wifi_connection_info(self.dut, expected_con)
+            expected_bssid = expected_con[WifiEnums.BSSID_KEY]
+            self.log.info("Roamed to %s successfully", expected_bssid)
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    def roaming_between_a_and_b_logic(self, config):
+        """Test roaming between two enterprise APs.
+
+        Steps:
+        1. Make bssid_a visible, bssid_b not visible.
+        2. Connect to ent_roaming_ssid. Expect DUT to connect to bssid_a.
+        3. Make bssid_a not visible, bssid_b visible.
+        4. Expect DUT to roam to bssid_b.
+        5. Make bssid_a visible, bssid_b not visible.
+        6. Expect DUT to roam back to bssid_a.
+        """
+        expected_con_to_a = {
+            WifiEnums.SSID_KEY: self.ent_roaming_ssid,
+            WifiEnums.BSSID_KEY: self.bssid_a,
+        }
+        expected_con_to_b = {
+            WifiEnums.SSID_KEY: self.ent_roaming_ssid,
+            WifiEnums.BSSID_KEY: self.bssid_b,
+        }
+        self.set_attns("a_on_b_off")
+        wutils.wifi_connect(self.dut, config)
+        wutils.verify_wifi_connection_info(self.dut, expected_con_to_a)
+        self.log.info("Roaming from %s to %s", self.bssid_a, self.bssid_b)
+        self.trigger_roaming_and_validate("b_on_a_off", expected_con_to_b)
+        self.log.info("Roaming from %s to %s", self.bssid_b, self.bssid_a)
+        self.trigger_roaming_and_validate("a_on_b_off", expected_con_to_a)
+
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="b15e4b3f-841d-428d-87ac-272f29f06e14")
+    def test_roaming_with_config_tls(self):
+        self.roaming_between_a_and_b_logic(self.config_tls)
+
+    @test_tracker_info(uuid="d349cfec-b4af-48b2-88b7-744f5de25d43")
+    def test_roaming_with_config_ttls_none(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        self.roaming_between_a_and_b_logic(config)
+
+    @test_tracker_info(uuid="89b8161c-754e-4138-831d-5fe40f521ce4")
+    def test_roaming_with_config_ttls_pap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        self.roaming_between_a_and_b_logic(config)
+
+    @test_tracker_info(uuid="d4925470-924b-4d03-8437-83e26b5f2df3")
+    def test_roaming_with_config_ttls_mschap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        self.roaming_between_a_and_b_logic(config)
+
+    @test_tracker_info(uuid="206b1327-dd9c-4742-8717-e7bf2a04eed6")
+    def test_roaming_with_config_ttls_mschapv2(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        self.roaming_between_a_and_b_logic(config)
+
+    @test_tracker_info(uuid="c2c0168b-2933-4954-af62-fb41f42dc45a")
+    def test_roaming_with_config_ttls_gtc(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        self.roaming_between_a_and_b_logic(config)
+
+    @test_tracker_info(uuid="481c4102-8f5b-4fcd-95cc-5e3285f47985")
+    def test_roaming_with_config_peap_mschapv2(self):
+        config = dict(self.config_peap)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        self.roaming_between_a_and_b_logic(config)
+
+    @test_tracker_info(uuid="404155d4-33a7-42b3-b369-5f2d63d19f16")
+    def test_roaming_with_config_peap_gtc(self):
+        config = dict(self.config_peap)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        self.roaming_between_a_and_b_logic(config)
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiEnterpriseTest.py b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
new file mode 100644
index 0000000..aee773c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
@@ -0,0 +1,765 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import pprint
+import random
+import time
+
+from acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+# EAP Macros
+EAP = WifiEnums.Eap
+EapPhase2 = WifiEnums.EapPhase2
+# Enterprise Config Macros
+Ent = WifiEnums.Enterprise
+
+
+class WifiEnterpriseTest(WifiBaseTest):
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        # If running in a setup with attenuators, set attenuation on all
+        # channels to zero.
+        if getattr(self, "attenuators", []):
+            for a in self.attenuators:
+                a.set_atten(0)
+        required_userparam_names = (
+            "ca_cert", "client_cert", "client_key", "passpoint_ca_cert",
+            "passpoint_client_cert", "passpoint_client_key", "eap_identity",
+            "eap_password", "invalid_ca_cert", "invalid_client_cert",
+            "invalid_client_key", "fqdn", "provider_friendly_name", "realm",
+            "device_password", "ping_addr", "radius_conf_2g", "radius_conf_5g",
+            "radius_conf_pwd")
+        self.unpack_userparams(required_userparam_names,
+                               roaming_consortium_ids=None,
+                               plmn=None,
+                               ocsp=0)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(
+                ent_network=True,
+                radius_conf_2g=self.radius_conf_2g,
+                radius_conf_5g=self.radius_conf_5g,
+                ent_network_pwd=True,
+                radius_conf_pwd=self.radius_conf_pwd,)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(
+                ent_network=True,
+                radius_conf_2g=self.radius_conf_2g,
+                radius_conf_5g=self.radius_conf_5g,
+                ent_network_pwd=True,
+                radius_conf_pwd=self.radius_conf_pwd,)
+        self.ent_network_2g = self.ent_networks[0]["2g"]
+        self.ent_network_5g = self.ent_networks[0]["5g"]
+        self.ent_network_pwd = self.ent_networks_pwd[0]["2g"]
+
+        # Default configs for EAP networks.
+        self.config_peap0 = {
+            Ent.EAP: int(EAP.PEAP),
+            Ent.CA_CERT: self.ca_cert,
+            Ent.IDENTITY: self.eap_identity,
+            Ent.PASSWORD: self.eap_password,
+            Ent.PHASE2: int(EapPhase2.MSCHAPV2),
+            WifiEnums.SSID_KEY: self.ent_network_5g[WifiEnums.SSID_KEY],
+            Ent.OCSP: self.ocsp,
+        }
+        self.config_peap1 = dict(self.config_peap0)
+        self.config_peap1[WifiEnums.SSID_KEY] = \
+            self.ent_network_2g[WifiEnums.SSID_KEY]
+        self.config_tls = {
+            Ent.EAP: int(EAP.TLS),
+            Ent.CA_CERT: self.ca_cert,
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+            Ent.CLIENT_CERT: self.client_cert,
+            Ent.PRIVATE_KEY_ID: self.client_key,
+            Ent.IDENTITY: self.eap_identity,
+            Ent.OCSP: self.ocsp,
+        }
+        self.config_ttls = {
+            Ent.EAP: int(EAP.TTLS),
+            Ent.CA_CERT: self.ca_cert,
+            Ent.IDENTITY: self.eap_identity,
+            Ent.PASSWORD: self.eap_password,
+            Ent.PHASE2: int(EapPhase2.MSCHAPV2),
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+            Ent.OCSP: self.ocsp,
+        }
+        self.config_pwd = {
+            Ent.EAP: int(EAP.PWD),
+            Ent.IDENTITY: self.eap_identity,
+            Ent.PASSWORD: self.eap_password,
+            WifiEnums.SSID_KEY: self.ent_network_pwd[WifiEnums.SSID_KEY],
+        }
+        self.config_sim = {
+            Ent.EAP: int(EAP.SIM),
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+        }
+        self.config_aka = {
+            Ent.EAP: int(EAP.AKA),
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+        }
+        self.config_aka_prime = {
+            Ent.EAP: int(EAP.AKA_PRIME),
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+        }
+
+        # Base config for passpoint networks.
+        self.config_passpoint = {
+            Ent.FQDN: self.fqdn,
+            Ent.FRIENDLY_NAME: self.provider_friendly_name,
+            Ent.REALM: self.realm,
+            Ent.CA_CERT: self.passpoint_ca_cert
+        }
+        if self.plmn:
+            self.config_passpoint[Ent.PLMN] = self.plmn
+        if self.roaming_consortium_ids:
+            self.config_passpoint[
+                Ent.ROAMING_IDS] = self.roaming_consortium_ids
+
+        # Default configs for passpoint networks.
+        self.config_passpoint_tls = dict(self.config_tls)
+        self.config_passpoint_tls.update(self.config_passpoint)
+        self.config_passpoint_tls[Ent.CLIENT_CERT] = self.passpoint_client_cert
+        self.config_passpoint_tls[
+            Ent.PRIVATE_KEY_ID] = self.passpoint_client_key
+        del self.config_passpoint_tls[WifiEnums.SSID_KEY]
+        self.config_passpoint_ttls = dict(self.config_ttls)
+        self.config_passpoint_ttls.update(self.config_passpoint)
+        del self.config_passpoint_ttls[WifiEnums.SSID_KEY]
+        # Set screen lock password so ConfigStore is unlocked.
+        self.dut.droid.setDevicePassword(self.device_password)
+
+    def teardown_class(self):
+        wutils.reset_wifi(self.dut)
+        self.dut.droid.disableDevicePassword(self.device_password)
+        self.dut.ed.clear_all_events()
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wifiStartTrackingStateChange()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.reset_wifi(self.dut)
+        self.dut.ed.clear_all_events()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        self.dut.droid.wifiStopTrackingStateChange()
+
+    """Helper Functions"""
+
+    def eap_negative_connect_logic(self, config, ad):
+        """Tries to connect to an enterprise network with invalid credentials
+        and expect a failure.
+
+        Args:
+            config: A dict representing an invalid EAP credential.
+
+        Returns:
+            True if connection failed as expected, False otherwise.
+        """
+        with asserts.assert_raises(signals.TestFailure, extras=config):
+            verdict = wutils.wifi_connect(ad, config)
+        asserts.explicit_pass("Connection failed as expected.")
+
+    def gen_negative_configs(self, config, neg_params):
+        """Generic function used to generate negative configs.
+
+        For all the valid configurations, if a param in the neg_params also
+        exists in a config, a copy of the config is made with an invalid value
+        of the param.
+
+        Args:
+            config: A valid configuration.
+            neg_params: A dict that has all the invalid values.
+
+        Returns:
+            An invalid configurations generated based on the valid
+            configuration. Each invalid configuration has a different invalid
+            field.
+        """
+        negative_config = dict(config)
+        if negative_config in [self.config_sim, self.config_aka,
+                               self.config_aka_prime]:
+            negative_config[WifiEnums.SSID_KEY] = 'wrong_hostapd_ssid'
+        for k, v in neg_params.items():
+            # Skip negative test for TLS's identity field since it's not
+            # used for auth.
+            if config[Ent.EAP] == EAP.TLS and k == Ent.IDENTITY:
+                continue
+            if k in config:
+                negative_config[k] = v
+                negative_config["invalid_field"] = k
+        return negative_config
+
+    def gen_negative_eap_configs(self, config):
+        """Generates invalid configurations for different EAP authentication
+        types.
+
+        For all the valid EAP configurations, if a param that is part of the
+        authentication info exists in a config, a copy of the config is made
+        with an invalid value of the param.
+
+        Args:
+            A valid network configration
+
+        Returns:
+            An invalid EAP configuration.
+        """
+        neg_params = {
+            Ent.CLIENT_CERT: self.invalid_client_cert,
+            Ent.CA_CERT: self.invalid_ca_cert,
+            Ent.PRIVATE_KEY_ID: self.invalid_client_key,
+            Ent.IDENTITY: "fake_identity",
+            Ent.PASSWORD: "wrong_password"
+        }
+        return self.gen_negative_configs(config, neg_params)
+
+    def gen_negative_passpoint_configs(self, config):
+        """Generates invalid configurations for different EAP authentication
+        types with passpoint support.
+
+        Args:
+            A valid network configration
+
+        Returns:
+            An invalid EAP configuration with passpoint fields.
+        """
+        neg_params = {
+            Ent.CLIENT_CERT: self.invalid_client_cert,
+            Ent.CA_CERT: self.invalid_ca_cert,
+            Ent.PRIVATE_KEY_ID: self.invalid_client_key,
+            Ent.IDENTITY: "fake_identity",
+            Ent.PASSWORD: "wrong_password",
+            Ent.FQDN: "fake_fqdn",
+            Ent.REALM: "where_no_one_has_gone_before",
+            Ent.PLMN: "fake_plmn",
+            Ent.ROAMING_IDS: [1234567890, 9876543210]
+        }
+        return self.gen_negative_configs(config, neg_params)
+
+    def eap_connect_toggle_wifi(self,
+                                config,
+                                *args):
+        """Connects to an enterprise network, toggles wifi state and ensures
+        that the device reconnects.
+
+        This logic expect the enterprise network to have Internet access.
+
+        Args:
+            config: A dict representing a wifi enterprise configuration.
+            args: args to be passed to |wutils.eap_connect|.
+
+        Returns:
+            True if the connection is successful and Internet access works.
+        """
+        ad = args[0]
+        wutils.wifi_connect(ad, config)
+        wutils.toggle_wifi_and_wait_for_reconnection(ad, config, num_of_tries=5)
+
+    """ Tests """
+
+    # EAP connect tests
+    """ Test connecting to enterprise networks of different authentication
+        types.
+
+        The authentication types tested are:
+            EAP-TLS
+            EAP-PEAP with different phase2 types.
+            EAP-TTLS with different phase2 types.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network.
+            2. Send a GET request to a website and check response.
+
+        Expect:
+            Successful connection and Internet access through the enterprise
+            networks.
+    """
+    @test_tracker_info(uuid="4e720cac-ea17-4de7-a540-8dc7c49f9713")
+    def test_eap_connect_with_config_tls(self):
+        wutils.wifi_connect(self.dut, self.config_tls)
+
+    @test_tracker_info(uuid="10e3a5e9-0018-4162-a9fa-b41500f13340")
+    def test_eap_connect_with_config_pwd(self):
+        wutils.wifi_connect(self.dut, self.config_pwd)
+
+    @test_tracker_info(uuid="b4513f78-a1c4-427f-bfc7-2a6b3da714b5")
+    def test_eap_connect_with_config_sim(self):
+        wutils.wifi_connect(self.dut, self.config_sim)
+
+    @test_tracker_info(uuid="7d390e30-cb67-4b55-bf00-567adad2d9b0")
+    def test_eap_connect_with_config_aka(self):
+        wutils.wifi_connect(self.dut, self.config_aka)
+
+    @test_tracker_info(uuid="742f921b-27c3-4b68-a3ca-88e64fe79c1d")
+    def test_eap_connect_with_config_aka_prime(self):
+        wutils.wifi_connect(self.dut, self.config_aka_prime)
+
+    @test_tracker_info(uuid="d34e30f3-6ef6-459f-b47a-e78ed90ce4c6")
+    def test_eap_connect_with_config_ttls_none(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="0dca3a15-472e-427c-8e06-4e38088ee973")
+    def test_eap_connect_with_config_ttls_pap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="47c4b459-2cb1-4fc7-b4e7-82534e8e090e")
+    def test_eap_connect_with_config_ttls_mschap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="fdb286c7-8069-481d-baf0-c5dd7a31ff03")
+    def test_eap_connect_with_config_ttls_mschapv2(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="d9315962-7987-4ee7-905d-6972c78ce8a1")
+    def test_eap_connect_with_config_ttls_gtc(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="90a67bd3-30da-4daf-8ab0-d964d7ad19be")
+    def test_eap_connect_with_config_peap0_mschapv2(self):
+        config = dict(self.config_peap0)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="3c451ba4-0c83-4eef-bc95-db4c21893008")
+    def test_eap_connect_with_config_peap0_gtc(self):
+        config = dict(self.config_peap0)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="6b45157d-0325-417a-af18-11af5d240d79")
+    def test_eap_connect_with_config_peap1_mschapv2(self):
+        config = dict(self.config_peap1)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="1663decc-71ae-4f95-a027-8a6dbf9c337f")
+    def test_eap_connect_with_config_peap1_gtc(self):
+        config = dict(self.config_peap1)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        wutils.wifi_connect(self.dut, config)
+
+    # EAP connect negative tests
+    """ Test connecting to enterprise networks.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network with invalid credentials.
+
+        Expect:
+            Fail to establish connection.
+    """
+    @test_tracker_info(uuid="b2a91f1f-ccd7-4bd1-ab81-19aab3d8ee38")
+    def test_eap_connect_negative_with_config_tls(self):
+        config = self.gen_negative_eap_configs(self.config_tls)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="6466abde-1d16-4168-9dd8-1e7a0a19889b")
+    def test_eap_connect_negative_with_config_pwd(self):
+        config = self.gen_negative_eap_configs(self.config_pwd)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="d7742a2a-85b0-409a-99d8-47711ddc5612")
+    def test_eap_connect_negative_with_config_sim(self):
+        config = self.gen_negative_eap_configs(self.config_sim)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="0ec0de93-cab3-4f41-960b-c0af64ff48c4")
+    def test_eap_connect_negative_with_config_aka(self):
+        config = self.gen_negative_eap_configs(self.config_aka)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="bb640ea4-32a6-48ea-87c9-f7128fffbbf6")
+    def test_eap_connect_negative_with_config_aka_prime(self):
+        config = self.gen_negative_eap_configs(self.config_aka_prime)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="86336ada-0ced-45a4-8a22-c4aa23c81111")
+    def test_eap_connect_negative_with_config_ttls_none(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="71e0498d-9973-4958-94bd-79051c328920")
+    def test_eap_connect_negative_with_config_ttls_pap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="c04142a8-b204-4d2d-98dc-150b16c8397e")
+    def test_eap_connect_negative_with_config_ttls_mschap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="625e7aa5-e3e6-4bbe-98c0-5aad8ca1555b")
+    def test_eap_connect_negative_with_config_ttls_mschapv2(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="24ea0d80-0a3f-41c2-8e05-d6387e589058")
+    def test_eap_connect_negative_with_config_ttls_gtc(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="b7c1f0f8-6338-4501-8e1d-c9b136aaba88")
+    def test_eap_connect_negative_with_config_peap0_mschapv2(self):
+        config = dict(self.config_peap0)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="9cf83dcb-38ad-4f75-9ea9-98de1cfaf7f3")
+    def test_eap_connect_negative_with_config_peap0_gtc(self):
+        config = dict(self.config_peap0)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="89bb2b6b-d073-402a-bdc1-68ac5f8752a3")
+    def test_eap_connect_negative_with_config_peap1_mschapv2(self):
+        config = dict(self.config_peap1)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="2252a864-9ff7-43b5-82d9-afe57d1f5e5f")
+    def test_eap_connect_negative_with_config_peap1_gtc(self):
+        config = dict(self.config_peap1)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        config = self.gen_negative_eap_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    # EAP connect config store tests
+    """ Test connecting to enterprise networks of different authentication
+        types after wifi toggle.
+
+        The authentication types tested are:
+            EAP-TLS
+            EAP-PEAP with different phase2 types.
+            EAP-TTLS with different phase2 types.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network.
+            2. Send a GET request to a website and check response.
+            3. Toggle wifi.
+            4. Ensure that the device reconnects to the same network.
+
+        Expect:
+            Successful connection and Internet access through the enterprise
+            networks.
+    """
+    @test_tracker_info(uuid="2a933b7f-27d7-4201-a34f-25b9d8072a8c")
+    def test_eap_connect_config_store_with_config_tls(self):
+        self.eap_connect_toggle_wifi(self.config_tls, self.dut)
+
+    @test_tracker_info(uuid="08dc071b-9fea-408a-a3f6-d3493869f6d4")
+    def test_eap_connect_config_store_with_config_pwd(self):
+        self.eap_connect_toggle_wifi(self.config_pwd, self.dut)
+
+    @test_tracker_info(uuid="230cb03e-58bc-41cb-b9b3-7215c2ab2325")
+    def test_eap_connect_config_store_with_config_sim(self):
+        self.eap_connect_toggle_wifi(self.config_sim, self.dut)
+
+    @test_tracker_info(uuid="dfc3e59c-2309-4598-8c23-bb3fe95ef89f")
+    def test_eap_connect_config_store_with_config_aka(self):
+        self.eap_connect_toggle_wifi(self.config_aka, self.dut)
+
+    @test_tracker_info(uuid="6050a1d1-4f3a-476d-bf93-638abd066790")
+    def test_eap_connect_config_store_with_config_aka_prime(self):
+        self.eap_connect_toggle_wifi(self.config_aka_prime, self.dut)
+
+    @test_tracker_info(uuid="03108057-cc44-4a80-8331-77c93694099c")
+    def test_eap_connect_config_store_with_config_ttls_none(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="53dd8195-e272-4589-a261-b8fa3607ad8d")
+    def test_eap_connect_config_store_with_config_ttls_pap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="640f697b-9c62-4b19-bd76-53b236a152e0")
+    def test_eap_connect_config_store_with_config_ttls_mschap(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="f0243684-fae0-46f3-afbd-bf525fc712e2")
+    def test_eap_connect_config_store_with_config_ttls_mschapv2(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="49ec7202-3b00-49c3-970a-201360888c74")
+    def test_eap_connect_config_store_with_config_ttls_gtc(self):
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="1c6abfa3-f344-4e28-b891-5481ab79efcf")
+    def test_eap_connect_config_store_with_config_peap0_mschapv2(self):
+        config = dict(self.config_peap0)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="2815bc76-49fa-43a5-a4b6-84788f9809d5")
+    def test_eap_connect_config_store_with_config_peap0_gtc(self):
+        config = dict(self.config_peap0)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="e93f7472-6895-4e36-bff2-9b2dcfd07ad0")
+    def test_eap_connect_config_store_with_config_peap1_mschapv2(self):
+        config = dict(self.config_peap1)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="6da72fa0-b858-4475-9559-46fe052d0d64")
+    def test_eap_connect_config_store_with_config_peap1_gtc(self):
+        config = dict(self.config_peap1)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    # Removing 'test_' for all passpoint based testcases as we want to disable
+    # them. Adding the valid test cases to self.tests make them run in serial
+    # (TODO): gmoturu - Update the passpoint tests to test the valid scenario
+    # Passpoint connect tests
+    """ Test connecting to enterprise networks of different authentication
+        types with passpoint support.
+
+        The authentication types tested are:
+            EAP-TLS
+            EAP-TTLS with MSCHAPV2 as phase2.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network.
+            2. Send a GET request to a website and check response.
+
+        Expect:
+            Successful connection and Internet access through the enterprise
+            networks with passpoint support.
+    """
+    @test_tracker_info(uuid="0b942524-bde9-4fc6-ac6a-fef1c247cb8e")
+    def passpoint_connect_with_config_passpoint_tls(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        wutils.wifi_connect(self.dut, self.config_passpoint_tls)
+
+    @test_tracker_info(uuid="33a014aa-99e7-4612-b732-54fabf1bf922")
+    def passpoint_connect_with_config_passpoint_ttls_none(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="1aba8bf9-2b09-4956-b418-c3f4dadab330")
+    def passpoint_connect_with_config_passpoint_ttls_pap(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="cd978fc9-a393-4b1e-bba3-1efc52224500")
+    def passpoint_connect_with_config_passpoint_ttls_mschap(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="bc311ee7-ba64-4c76-a629-b916701bf6a5")
+    def passpoint_connect_with_config_passpoint_ttls_mschapv2(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        wutils.wifi_connect(self.dut, config)
+
+    @test_tracker_info(uuid="357e5162-5081-4149-bedd-ef2c0f88b97e")
+    def passpoint_connect_with_config_passpoint_ttls_gtc(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        wutils.wifi_connect(self.dut, config)
+
+    # Passpoint connect negative tests
+    """ Test connecting to enterprise networks.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network with invalid credentials.
+
+        Expect:
+            Fail to establish connection.
+    """
+    @test_tracker_info(uuid="7b6b44a0-ff70-49b4-94ca-a98bedc18f92")
+    def passpoint_connect_negative_with_config_passpoint_tls(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = self.gen_negative_passpoint_configs(self.config_passpoint_tls)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="3dbde40a-e88c-4166-b932-163663a10a41")
+    def passpoint_connect_negative_with_config_passpoint_ttls_none(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        config = self.gen_negative_passpoint_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="8ee22ad6-d561-4ca2-a808-9f372fce56b4")
+    def passpoint_connect_negative_with_config_passpoint_ttls_pap(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        config = self.gen_negative_passpoint_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="db5cefe7-9cb8-47a6-8635-006c80b97012")
+    def passpoint_connect_negative_with_config_passpoint_ttls_mschap(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        config = self.gen_negative_passpoint_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="8f49496e-80df-48ce-9c51-42f0c6b81aff")
+    def passpoint_connect_negative_with_config_passpoint_ttls_mschapv2(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        config = self.gen_negative_passpoint_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    @test_tracker_info(uuid="6561508f-598e-408d-96b6-15b631664be6")
+    def passpoint_connect_negative_with_config_passpoint_ttls_gtc(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        config = self.gen_negative_passpoint_configs(config)
+        self.eap_negative_connect_logic(config, self.dut)
+
+    # Passpoint connect config store tests
+    """ Test connecting to enterprise networks of different authentication
+        types with passpoint support after wifi toggle.
+
+        The authentication types tested are:
+            EAP-TLS
+            EAP-TTLS with MSCHAPV2 as phase2.
+
+        Procedures:
+            For each enterprise wifi network
+            1. Connect to the network.
+            2. Send a GET request to a website and check response.
+            3. Toggle wifi.
+            4. Ensure that the device reconnects to the same network.
+
+        Expect:
+            Successful connection and Internet access through the enterprise
+            networks with passpoint support.
+    """
+    @test_tracker_info(uuid="5d5e6bb0-faea-4a6e-a6bc-c87de997a4fd")
+    def passpoint_connect_config_store_with_config_passpoint_tls(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        self.eap_connect_toggle_wifi(self.config_passpoint_tls, self.dut)
+
+    @test_tracker_info(uuid="0c80262d-23c1-439f-ad64-7b8ada5d1962")
+    def passpoint_connect_config_store_with_config_passpoint_ttls_none(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.NONE.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="786e424c-b5a6-4fe9-a951-b3de16ebb6db")
+    def passpoint_connect_config_store_with_config_passpoint_ttls_pap(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="22fd61bf-722a-4016-a778-fc33e94ed211")
+    def passpoint_connect_config_store_with_config_passpoint_ttls_mschap(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAP.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="2abd348c-9c66-456b-88ad-55f971717620")
+    def passpoint_connect_config_store_with_config_passpoint_ttls_mschapv2(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
+        self.eap_connect_toggle_wifi(config, self.dut)
+
+    @test_tracker_info(uuid="043e8cdd-db95-4f03-b308-3c8cecf874b1")
+    def passpoint_connect_config_store_with_config_passpoint_ttls_gtc(self):
+        asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(),
+                        "Passpoint is not supported on %s" % self.dut.model)
+        config = dict(self.config_passpoint_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.GTC.value
+        self.eap_connect_toggle_wifi(config, self.dut)
diff --git a/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
new file mode 100644
index 0000000..da2c066
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class WifiHiddenSSIDTest(WifiBaseTest):
+    """Tests for APIs in Android's WifiManager class.
+
+    Test Bed Requirement:
+    * One Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(hidden=True)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                owe_network=True,
+                                                sae_network=True,
+                                                hidden=True)
+
+        self.open_hidden_2g = self.open_network[0]["2g"]
+        self.open_hidden_5g = self.open_network[0]["5g"]
+        self.wpa_hidden_2g = self.reference_networks[0]["2g"]
+        self.wpa_hidden_5g = self.reference_networks[0]["5g"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+
+    def teardown_class(self):
+        wutils.reset_wifi(self.dut)
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+
+    def check_hiddenSSID_in_scan(self, ap_ssid, max_tries=2):
+        """Check if the ap started by wifi tethering is seen in scan results.
+
+        Args:
+            ap_ssid: SSID of the ap we are looking for.
+            max_tries: Number of scans to try.
+        Returns:
+            True: if ap_ssid is found in scan results.
+            False: if ap_ssid is not found in scan results.
+        """
+        for num_tries in range(max_tries):
+            wutils.start_wifi_connection_scan(self.dut)
+            scan_results = self.dut.droid.wifiGetScanResults()
+            match_results = wutils.match_networks(
+                {wutils.WifiEnums.SSID_KEY: ap_ssid}, scan_results)
+            if len(match_results) > 0:
+                return True
+        return False
+
+    def add_hiddenSSID_and_connect(self, hidden_network):
+        """Add the hidden network and connect to it.
+
+        Args:
+            hidden_network: The hidden network config to connect to.
+
+        """
+        wutils.connect_to_wifi_network(self.dut, hidden_network, hidden=True)
+        if not wutils.validate_connection(self.dut):
+            raise signals.TestFailure("Fail to connect to internet on %s" %
+                                       hidden_network)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="d0871f98-6049-4937-a288-ec4a2746c771")
+    def test_connect_to_wpa_hidden_2g(self):
+        """Connect to a WPA, 2G network.
+
+        Steps:
+        1. Add a WPA, 2G hidden network.
+        2. Ensure the network is visible in scan.
+        3. Connect and run ping.
+
+        """
+        self.add_hiddenSSID_and_connect(self.wpa_hidden_2g)
+
+    @test_tracker_info(uuid="c558b31a-549a-4012-9052-275623992187")
+    def test_connect_to_wpa_hidden_5g(self):
+        """Connect to a WPA, 5G hidden  network.
+
+        Steps:
+        1. Add a WPA, 5G hidden network.
+        2. Ensure the network is visible in scan.
+        3. Connect and run ping.
+
+        """
+        self.add_hiddenSSID_and_connect(self.wpa_hidden_5g)
+
+    @test_tracker_info(uuid="cdfce76f-6374-439d-aa1d-e920508269d2")
+    def test_connect_to_open_hidden_2g(self):
+        """Connect to a Open, 2G hidden  network.
+
+        Steps:
+        1. Add a Open, 2G hidden network.
+        2. Ensure the network is visible in scan.
+        3. Connect and run ping.
+
+        """
+        self.add_hiddenSSID_and_connect(self.open_hidden_2g)
+
+    @test_tracker_info(uuid="29ccbae4-4382-4df8-8fc5-00e3104230d0")
+    def test_connect_to_open_hidden_5g(self):
+        """Connect to a Open, 5G hidden  network.
+
+        Steps:
+        1. Add a Open, 5G hidden network.
+        2. Ensure the network is visible in scan.
+        3. Connect and run ping.
+
+        """
+        self.add_hiddenSSID_and_connect(self.open_hidden_5g)
+
+    @test_tracker_info(uuid="62b664df-6397-4360-97bf-a8095c23a878")
+    def test_connect_to_wpa3_owe_hidden_2g(self):
+        """Connect to WPA3 OWE 2G hidden wifi network."""
+        self.add_hiddenSSID_and_connect(self.owe_networks[0]["2g"])
+
+    @test_tracker_info(uuid="dd7b029d-c008-4288-a91c-0820b0b3f29d")
+    def test_connect_to_wpa3_owe_hidden_5g(self):
+        """Connect to WPA3 OWE 5G hidden wifi network."""
+        self.add_hiddenSSID_and_connect(self.owe_networks[0]["5g"])
+
+    @test_tracker_info(uuid="1a9f3ee8-3db0-4f07-a604-66c14a897f94")
+    def test_connect_to_wpa3_sae_hidden_2g(self):
+        """Connect to WPA3 SAE 2G hidden wifi network."""
+        self.add_hiddenSSID_and_connect(self.sae_networks[0]["2g"])
+
+    @test_tracker_info(uuid="6c75618b-9c9b-4eb6-a922-ef1719704a9c")
+    def test_connect_to_wpa3_sae_hidden_5g(self):
+        """Connect to WPA3 SAE 5G hidden wifi network."""
+        self.add_hiddenSSID_and_connect(self.sae_networks[0]["5g"])
diff --git a/acts_tests/tests/google/wifi/WifiIFSTwTest.py b/acts_tests/tests/google/wifi/WifiIFSTwTest.py
new file mode 100644
index 0000000..ac2022f
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiIFSTwTest.py
@@ -0,0 +1,297 @@
+#
+#  Copyright 2019 - The Android Open Source Project
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import math
+import os
+
+import time
+
+import threading
+from acts import utils
+from acts import signals
+from acts import asserts
+from acts.controllers import attenuator
+from acts.controllers.sl4a_lib import rpc_client
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net.net_test_utils import start_tcpdump, stop_tcpdump
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.wifi_test_utils import WifiEnums
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.utils import stop_standing_subprocess
+
+TCPDUMP_PATH = '/data/local/tmp/tcpdump'
+
+
+class WifiIFSTwTest(WifiBaseTest):
+    """Tests for wifi IFS
+
+        Test Bed Requirement:
+            *One Android device
+            *Two Visible Wi-Fi Access Points
+            *One attenuator with 4 ports
+    """
+
+    def setup_class(self):
+        """Setup required dependencies from config file and configure
+        the required networks for testing roaming.
+
+        Returns:
+            True if successfully configured the requirements for testing.
+        """
+        super().setup_class()
+        self.simulation_thread_running = False
+        self.atten_roaming_count = 0
+        self.start_db = 30
+        self.roaming_cycle_seconds = 20
+        self.fail_count = 0
+        self.retry_pass_count = 0
+        self.ping_count = 0
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ["attenuators", "ifs_params"]
+        opt_param = []
+
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(ap_count=2, same_ssid=True)
+
+        wutils.wifi_toggle_state(self.dut, True)
+        if "ifs_params" in self.user_params:
+            self.attn_start_db = self.ifs_params[0]["start_db"]
+            self.gateway = self.ifs_params[0]["gateway"]
+            self.roaming_cycle_seconds = self.ifs_params[0][
+                "roaming_cycle_seconds"]
+            self.total_test_hour = self.ifs_params[0]["total_test_hour"]
+            self.log_capture_period_hour = self.ifs_params[0][
+                "log_capture_period_hour"]
+            self.on_active_port = self.ifs_params[0]["on_active_port"]
+            asserts.assert_true(
+                len(self.on_active_port) == 2, "Need setup 2 port.")
+
+        self.tcpdump_pid = None
+        utils.set_location_service(self.dut, True)
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        self.dut.unlock_screen()
+        self.tcpdump_pid = start_tcpdump(self.dut, self.test_name)
+
+    def teardown_class(self):
+        self.dut.ed.clear_all_events()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def simulate_roaming(self):
+        """
+        To simulate user move between ap1 and ap2:
+
+        1. Move to ap2:
+            Set ap1's signal attenuation gradually changed from 0 to max_db
+            Set ap2's signal attenuation gradually changed from start_db to 0
+
+        2. Then move to ap1:
+            Set ap1's signal attenuation gradually changed from start_db to 0
+            Set ap2's signal attenuation gradually changed from 0 to max_db
+
+        * 0<start_db<max_db
+        """
+        attn_max = 95
+        attn_min = 0
+
+        #on_active_port value should between [0-1,2-3]
+        active_attenuator = {
+            "1": self.attenuators[self.on_active_port[0]],
+            "2": self.attenuators[self.on_active_port[1]]
+        }
+
+        for attenuator in self.attenuators:
+            attenuator.set_atten(attn_max)
+
+        self.simulation_thread_running = True
+        while self.simulation_thread_running:
+            active_attenuator["1"].set_atten(attn_min)
+            active_attenuator["2"].set_atten(attn_max)
+            self.log_attens()
+            time.sleep(10)
+
+            active_attenuator["2"].set_atten(self.start_db)
+            self.log_attens()
+            time.sleep(5)
+            for i in range(self.roaming_cycle_seconds):
+                db1 = math.ceil(attn_max / self.roaming_cycle_seconds *
+                                (i + 1))
+                db2 = self.start_db - math.ceil(
+                    self.start_db / self.roaming_cycle_seconds * (i + 1))
+                active_attenuator["1"].set_atten(db1)
+                active_attenuator["2"].set_atten(db2)
+                self.log_attens()
+                time.sleep(1)
+
+            active_attenuator["1"].set_atten(self.start_db)
+            self.log_attens()
+            time.sleep(5)
+            for i in range(self.roaming_cycle_seconds):
+                db1 = math.ceil(attn_max / self.roaming_cycle_seconds *
+                                (i + 1))
+                db2 = self.start_db - math.ceil(
+                    self.start_db / self.roaming_cycle_seconds * (i + 1))
+                active_attenuator["1"].set_atten(db2)
+                active_attenuator["2"].set_atten(db1)
+                self.log_attens()
+                time.sleep(1)
+            self.atten_roaming_count += 1
+
+    def catch_log(self):
+        """Capture logs include bugreport, ANR, mount,ps,vendor,tcpdump"""
+
+        self.log.info("Get log for regular capture.")
+        file_name = time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime())
+        current_path = os.path.join(self.dut.log_path, file_name)
+        os.makedirs(current_path, exist_ok=True)
+        serial_number = self.dut.serial
+
+        try:
+            out = self.dut.adb.shell("bugreportz", timeout=240)
+            if not out.startswith("OK"):
+                raise AndroidDeviceError(
+                    'Failed to take bugreport on %s: %s' % (serial_number,
+                                                            out),
+                    serial=serial_number)
+            br_out_path = out.split(':')[1].strip().split()[0]
+            self.dut.adb.pull("%s %s" % (br_out_path, self.dut.log_path))
+            self.dut.adb.pull("/data/anr {}".format(current_path), timeout=600)
+            self.dut.adb._exec_adb_cmd("shell", "mount > {}".format(
+                os.path.join(current_path, "mount.txt")))
+            self.dut.adb._exec_adb_cmd("shell", "ps > {}".format(
+                os.path.join(current_path, "ps.txt")))
+            self.dut.adb.pull("/data/misc/logd {}".format(current_path))
+            self.dut.adb.pull(
+                "/data/vendor {}".format(current_path), timeout=800)
+            stop_tcpdump(
+                self.dut, self.tcpdump_pid, file_name, adb_pull_timeout=600)
+            self.tcpdump_pid = start_tcpdump(self.dut, file_name)
+        except TimeoutError as e:
+            self.log.error(e)
+
+    def http_request(self, url="https://www.google.com/"):
+        """Get the result via string from target url
+
+        Args:
+            url: target url to loading
+
+        Returns:
+            True if http_request pass
+        """
+
+        self.ping_count += 1
+        try:
+            self.dut.droid.httpRequestString(url)
+            self.log.info("httpRequest Finish")
+            time.sleep(1)
+            return True
+        except rpc_client.Sl4aApiError as e:
+            self.log.warning("httpRequest Fail.")
+            self.log.warning(e)
+            # Set check delay if http request fail during device roaming.
+            # Except finish roaming within 10s.
+            time.sleep(10)
+            self.log.warning("Ping Google DNS response : {}".format(
+                self.can_ping("8.8.8.8")))
+            for gate in self.gateway:
+                ping_result = self.can_ping(gate)
+                self.log.warning("Ping AP Gateway[{}] response : {}".format(
+                    gate, ping_result))
+                if ping_result:
+                    self.retry_pass_count += 1
+                    return True
+            self.fail_count += 1
+            return False
+
+    def log_attens(self):
+        """Log DB from channels"""
+
+        attenuation = ', '.join('{:>5.2f}dB '.format(atten.get_atten())
+                                for atten in self.attenuators)
+        self.log.debug('[Attenuation] %s', attenuation)
+
+    def can_ping(self, ip_addr):
+        """A function to check ping pass.
+
+        Args:
+            ip_addr: target ip address to ping
+
+        Returns:
+            True if ping pass
+        """
+        ping_result = self.dut.adb.shell("ping -c 1 {}".format(ip_addr))
+        return '0%' in ping_result.split(' ')
+
+    def browsing_test(self, stress_hour_time):
+        """Continue stress http_request and capture log if any fail
+
+        Args:
+            stress_hour_time: hour of time to stress http_request
+        """
+        t = threading.Thread(target=self.simulate_roaming)
+        t.start()
+        start_time = time.time()
+        http_request_failed = False
+        while time.time() < start_time + stress_hour_time * 3600:
+            if not self.http_request():
+                http_request_failed = True
+        self.simulation_thread_running = False
+        t.join()
+        if http_request_failed:
+            self.catch_log()
+        else:
+            stop_standing_subprocess(self.tcpdump_pid)
+            file_name = time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime())
+            self.tcpdump_pid = start_tcpdump(self.dut, file_name)
+
+    def test_roaming(self):
+        network = self.reference_networks[0]["2g"]
+        wutils.connect_to_wifi_network(self.dut, network)
+
+        time.sleep(10)
+        test_time_slot = int(
+            self.total_test_hour / self.log_capture_period_hour)
+        edge_time_slot = int(
+            self.total_test_hour % self.log_capture_period_hour)
+
+        for i in range(test_time_slot):
+            self.browsing_test(self.log_capture_period_hour)
+        if edge_time_slot:
+            self.browsing_test(edge_time_slot)
+
+        self.log.info("Total roaming times: {}".format(
+            self.atten_roaming_count))
+        self.log.info("Total ping times: {}".format(self.ping_count))
+        self.log.info("Retry pass times: {}".format(self.retry_pass_count))
+        self.log.info("Total fail times: {}".format(self.fail_count))
+        if self.fail_count:
+            signals.TestFailure(
+                'Find roaming fail condition',
+                extras={
+                    'Roaming fail times': self.fail_count
+                })
diff --git a/acts_tests/tests/google/wifi/WifiIOTTest.py b/acts_tests/tests/google/wifi/WifiIOTTest.py
new file mode 100644
index 0000000..3ea3b3b
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiIOTTest.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import time
+
+import acts.signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiIOTTest(WifiBaseTest):
+    """ Tests for wifi IOT
+
+        Test Bed Requirement:
+          * One Android device
+          * Wi-Fi IOT networks visible to the device
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+
+        req_params = [ "iot_networks", ]
+        opt_params = [ "open_network", "iperf_server_address" ]
+        self.unpack_userparams(req_param_names=req_params,
+                               opt_param_names=opt_params)
+
+        asserts.assert_true(
+            len(self.iot_networks) > 0,
+            "Need at least one iot network with psk.")
+
+        if getattr(self, 'open_network', False):
+            self.iot_networks.append(self.open_network)
+
+        wutils.wifi_toggle_state(self.dut, True)
+        if "iperf_server_address" in self.user_params:
+            self.iperf_server = self.iperf_servers[0]
+            self.iperf_server.start()
+
+        # create hashmap for testcase name and SSIDs
+        self.iot_test_prefix = "test_iot_connection_to_"
+        self.ssid_map = {}
+        for network in self.iot_networks:
+            SSID = network['SSID'].replace('-','_')
+            self.ssid_map[SSID] = network
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        if "iperf_server_address" in self.user_params:
+            self.iperf_server.stop()
+
+    """Helper Functions"""
+
+    def connect_to_wifi_network(self, network):
+        """Connection logic for open and psk wifi networks.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        SSID = network[WifiEnums.SSID_KEY]
+        self.dut.ed.clear_all_events()
+        wutils.start_wifi_connection_scan(self.dut)
+        scan_results = self.dut.droid.wifiGetScanResults()
+        wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
+        wutils.wifi_connect(self.dut, network, num_of_tries=3)
+
+    def run_iperf_client(self, network):
+        """Run iperf traffic after connection.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        if "iperf_server_address" in self.user_params:
+            wait_time = 5
+            SSID = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic through {}".format(SSID))
+            time.sleep(wait_time)
+            port_arg = "-p {}".format(self.iperf_server.port)
+            success, data = self.dut.run_iperf_client(self.iperf_server_address,
+                                                      port_arg)
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+
+    def connect_to_wifi_network_and_run_iperf(self, network):
+        """Connection logic for open and psk wifi networks.
+
+        Logic steps are
+        1. Connect to the network.
+        2. Run iperf traffic.
+
+        Args:
+            params: A dictionary with network info.
+        """
+        self.connect_to_wifi_network(network)
+        self.run_iperf_client(network)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="a57cc861-b6c2-47e4-9db6-7a3ab32c6e20")
+    def test_iot_connection_to_ubiquity_ap1_2g(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="2065c2f7-2b89-4da7-a15d-e5dc17b88d52")
+    def test_iot_connection_to_ubiquity_ap1_5g(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="6870e35b-f7a7-45bf-b021-fea049ae53de")
+    def test_iot_connection_to_AirportExpress_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="95f4b405-79d7-4873-a152-4384acc88f41")
+    def test_iot_connection_to_AirportExpress_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="02a8cc75-6781-4153-8d90-bed7568a1e78")
+    def test_iot_connection_to_AirportExtreme_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="83a42c97-1358-4ba7-bdb2-238fdb1c945e")
+    def test_iot_connection_to_AirportExtreme_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="d56cc46a-f772-4c96-b84e-4e05c82f5f9d")
+    def test_iot_connection_to_AirportExtremeOld_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="4b57277d-ea96-4379-bd71-8b4f03253ec8")
+    def test_iot_connection_to_AirportExtremeOld_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="2503d9ed-35df-4be0-b838-590324cecaee")
+    def iot_connection_to_Dlink_AC1200_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="0a44e148-a4bf-43f4-88eb-e4c1ffa850ce")
+    def iot_connection_to_Dlink_AC1200_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="6bd77417-089f-4fb1-b4c2-2cd673c64bcb")
+    def test_iot_connection_to_Dlink_AC3200_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="9fbff6e7-36c8-4342-9c29-bce6a8ef04ec")
+    def test_iot_connection_to_Dlink_AC3200_5G_1(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="bfccdaa9-8e01-488c-9768-8c71ab5ec157")
+    def test_iot_connection_to_Dlink_AC3200_5G_2(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="0e4978de-0435-4856-ae5a-c39cc64e375b")
+    def test_iot_connection_to_Dlink_N750_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="cdb82797-9981-4ba6-8958-025f59c60e83")
+    def test_iot_connection_to_Dlink_N750_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="0bf8f129-eb96-4b1e-94bd-8dd93e8731e3")
+    def iot_connection_to_Linksys_E800_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="f231216d-6ab6-46b7-a0a5-1ac15935e412")
+    def test_iot_connection_to_Linksys_AC1900_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="5acd4bec-b210-4b4c-8b2c-60f3f67798a9")
+    def test_iot_connection_to_Linksys_AC1900_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="f4fd9877-b13f-47b0-9523-1ce363200c2f")
+    def iot_connection_to_Linksys_AC2400_2g(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="438d679a-4f6c-476d-9eba-63b6f1f2bef4")
+    def iot_connection_to_Linksys_AC2400_5g(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="b9bc00d8-46c5-4c5e-bd58-93ab1ca8d53b")
+    def iot_connection_to_NETGEAR_AC1900_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="fb4c7d80-4c12-4b08-a40a-2745e2bd167b")
+    def iot_connection_to_NETGEAR_AC1900_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="054d2ffc-97fd-4613-bf47-acedd0fa4701")
+    def test_iot_connection_to_NETGEAR_AC3200_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="d15a789a-def5-4c6a-b59e-1a75f73cc6a9")
+    def test_iot_connection_to_NETGEAR_AC3200_5G_1(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="1de6369e-97da-479f-b17c-9144bb814f51")
+    def test_iot_connection_to_NETGEAR_AC3200_5G_2(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="008ec18e-fd48-4359-8a0d-223c921a1faa")
+    def iot_connection_to_NETGEAR_N300_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="c61eeaf0-af02-46bf-bcec-871e2f9dee71")
+    def iot_connection_to_WNDR4500v2_AES_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="dcad3474-4022-48bc-8529-07321611b616")
+    def iot_connection_to_WNDR4500v2_WEP_SHARED128_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="3573a880-4542-4dea-9909-aa2f9865a059")
+    def iot_connection_to_ARCHER_WEP_OPEN_64_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="9c15c52e-945a-4b9b-bf0e-5bd6293dad1c")
+    def iot_connection_to_ARCHER_WEP_OPEN_128_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="e5517b82-c225-449d-83ac-055a561a764f")
+    def test_iot_connection_to_TP_LINK_AC1700_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="9531d3cc-129d-4501-a5e3-d7502120cd8b")
+    def test_iot_connection_to_TP_LINK_AC1700_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="eab810d4-8e07-49c9-86c1-cb8d1a0285d0")
+    def iot_connection_to_TP_LINK_N300_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="05d4cb25-a58d-46b4-a5ff-6e3fe28f2b16")
+    def iot_connection_to_fritz_7490_5g(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="8333e5e6-72fd-4957-bab0-fa45ce1d765a")
+    def iot_connection_to_NETGEAR_R8500_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="c88053fb-730f-4447-a802-1fb9721f69df")
+    def iot_connection_to_NETGEAR_R8500_5G1(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="f5d1e44b-396b-4976-bb0c-160bdce89a59")
+    def iot_connection_to_NETGEAR_R8500_5G2(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="7c12f943-d9e2-45b1-aa84-fcb43efbbb04")
+    def test_iot_connection_to_TP_LINK_5504_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="52be6b76-5e43-4289-83e1-4cd0d995d39b")
+    def test_iot_connection_to_TP_LINK_5504_5G_1(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="0b43d1da-e207-443d-b16c-c4ee3e924036")
+    def test_iot_connection_to_TP_LINK_5504_5G_2(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="4adcef5c-589a-4398-a28c-28a56d762f72")
+    def test_iot_connection_to_TP_LINK_C2600_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="3955a443-505b-4015-9daa-f52abbad8377")
+    def test_iot_connection_to_TP_LINK_C2600_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="3e9115dd-adb6-40a4-9831-dca8f1f32abe")
+    def test_iot_connection_to_Linksys06832_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="5dca028a-7384-444f-b231-973054afe215")
+    def test_iot_connection_to_Linksys06832_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="e639f6db-ad8e-4b4f-91f3-10acdf93142a")
+    def test_iot_connection_to_AmpedAthena_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="3dd90d80-952f-4f17-a48a-fe42e7d6e1ff")
+    def test_iot_connection_to_AmpedAthena_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="b9babe3a-ecba-4c5c-bc6b-0ba48c744e66")
+    def test_iot_connection_to_ASUS_AC3100_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="f8f06f92-821d-4e80-8f1e-efb6c6adc12a")
+    def test_iot_connection_to_ASUS_AC3100_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="f4d227df-1151-469a-b01c-f4b1c1f7a84b")
+    def iot_connection_to_NETGEAR_WGR614_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
diff --git a/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py b/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
new file mode 100644
index 0000000..ed0d8d7
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
@@ -0,0 +1,360 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import time
+
+import acts.signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.controllers import iperf_server as ipf
+
+import json
+import logging
+import math
+import os
+from acts import utils
+import csv
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiIOTTwPkg1Test(WifiBaseTest):
+    """ Tests for wifi IOT
+
+        Test Bed Requirement:
+          * One Android device
+          * Wi-Fi IOT networks visible to the device
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+
+        req_params = [ "iot_networks", ]
+        opt_params = [ "open_network",
+                       "iperf_server_address","iperf_port_arg",
+                       "pdu_address" , "pduon_wait_time","pduon_address"
+        ]
+        self.unpack_userparams(req_param_names=req_params,
+                               opt_param_names=opt_params)
+
+        asserts.assert_true(
+            len(self.iot_networks) > 0,
+            "Need at least one iot network with psk.")
+
+        if getattr(self, 'open_network', False):
+            self.iot_networks.append(self.open_network)
+
+        wutils.wifi_toggle_state(self.dut, True)
+        if "iperf_server_address" in self.user_params:
+            self.iperf_server = self.iperf_servers[0]
+
+        # create hashmap for testcase name and SSIDs
+        self.iot_test_prefix = "test_iot_connection_to_"
+        self.ssid_map = {}
+        for network in self.iot_networks:
+            SSID = network['SSID'].replace('-','_')
+            self.ssid_map[SSID] = network
+
+        # create folder for IOT test result
+        self.log_path = os.path.join(logging.log_path, "IOT_results")
+        os.makedirs(self.log_path, exist_ok=True)
+
+        Header=("test_name","throughput_TX","throughput_RX")
+        self.csv_write(Header)
+
+        # check pdu_address
+        if "pdu_address" and "pduon_wait_time" in self.user_params:
+            self.pdu_func()
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        if "iperf_server_address" in self.user_params:
+            self.iperf_server.stop()
+
+    """Helper Functions"""
+
+    def connect_to_wifi_network(self, network):
+        """Connection logic for open and psk wifi networks.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        SSID = network[WifiEnums.SSID_KEY]
+        self.dut.ed.clear_all_events()
+        wutils.start_wifi_connection_scan(self.dut)
+        scan_results = self.dut.droid.wifiGetScanResults()
+        wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
+        wutils.wifi_connect(self.dut, network, num_of_tries=3)
+
+    def run_iperf_client(self, network):
+        """Run iperf TX throughput after connection.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        if "iperf_server_address" in self.user_params:
+
+            # Add iot_result
+            iot_result = []
+            self.iperf_server.start(tag="TX_server_{}".format(
+                self.current_test_name))
+            wait_time = 5
+            SSID = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic TX through {}".format(SSID))
+            time.sleep(wait_time)
+            port_arg = "-p {} -J {}".format(self.iperf_server.port,self.iperf_port_arg)
+            success, data = self.dut.run_iperf_client(self.iperf_server_address,
+                                                      port_arg)
+            # Parse and log result
+            client_output_path = os.path.join(
+                self.iperf_server.log_path, "IperfDUT,{},TX_client_{}".format(
+                    self.iperf_server.port,self.current_test_name))
+            with open(client_output_path, 'w') as out_file:
+                out_file.write("\n".join(data))
+            self.iperf_server.stop()
+
+            iperf_file = self.iperf_server.log_files[-1]
+            try:
+                iperf_result = ipf.IPerfResult(iperf_file)
+                curr_throughput = math.fsum(iperf_result.instantaneous_rates)
+            except:
+                self.log.warning(
+                    "ValueError: Cannot get iperf result. Setting to 0")
+                curr_throughput = 0
+            iot_result.append(curr_throughput)
+            self.log.info("Throughput is {0:.2f} Mbps".format(curr_throughput))
+
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+            return iot_result
+
+    def run_iperf_server(self, network):
+        """Run iperf RX throughput after connection.
+
+        Args:
+            params: Dictionary with network info.
+
+        Returns:
+            iot_result: dict containing iot_results
+        """
+        if "iperf_server_address" in self.user_params:
+
+            # Add iot_result
+            iot_result = []
+            self.iperf_server.start(tag="RX_client_{}".format(
+                self.current_test_name))
+            wait_time = 5
+            SSID = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic RX through {}".format(SSID))
+            time.sleep(wait_time)
+            port_arg = "-p {} -J -R {}".format(self.iperf_server.port,self.iperf_port_arg)
+            success, data = self.dut.run_iperf_client(self.iperf_server_address,
+                                                      port_arg)
+            client_output_path = os.path.join(
+                self.iperf_server.log_path, "IperfDUT,{},RX_server_{}".format(
+                    self.iperf_server.port,self.current_test_name))
+            with open(client_output_path, 'w') as out_file:
+                out_file.write("\n".join(data))
+            self.iperf_server.stop()
+
+            iperf_file = client_output_path
+            try:
+                iperf_result = ipf.IPerfResult(iperf_file)
+                curr_throughput = math.fsum(iperf_result.instantaneous_rates)
+            except:
+                self.log.warning(
+                    "ValueError: Cannot get iperf result. Setting to 0")
+                curr_throughput = 0
+            iot_result.append(curr_throughput)
+            self.log.info("Throughput is {0:.2f} Mbps".format(curr_throughput))
+
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+            return iot_result
+
+    def iperf_test_func(self,network):
+        """Main function to test iperf TX/RX.
+
+        Args:
+            params: Dictionary with network info
+        """
+        # Initialize
+        iot_result = {}
+
+        # Run RvR and log result
+        iot_result["throughput_TX"] = self.run_iperf_client(network)
+        iot_result["throughput_RX"] = self.run_iperf_server(network)
+        iot_result["test_name"] = self.current_test_name
+
+        # Save output as text file
+        results_file_path = "{}/{}.json".format(self.log_path,
+                                                self.current_test_name)
+        with open(results_file_path, 'w') as results_file:
+            json.dump(iot_result, results_file, indent=4)
+
+        data=(iot_result["test_name"],iot_result["throughput_TX"][0],
+              iot_result["throughput_RX"][0])
+        self.csv_write(data)
+
+    def csv_write(self,data):
+        with open("{}/Result.csv".format(self.log_path), "a", newline="") as csv_file:
+            csv_writer = csv.writer(csv_file,delimiter=',')
+            csv_writer.writerow(data)
+            csv_file.close()
+
+    def pdu_func(self):
+        """control Power Distribution Units on local machine.
+
+        Logic steps are
+        1. Turn off PDU for all port.
+        2. Turn on PDU for specified port.
+        """
+        out_file_name = "PDU.log"
+        self.full_out_path = os.path.join(self.log_path, out_file_name)
+        cmd = "curl http://snmp:1234@{}/offs.cgi?led=11111111> {}".format(self.pdu_address,
+                                                                          self.full_out_path)
+        self.pdu_process = utils.start_standing_subprocess(cmd)
+        wait_time = 10
+        self.log.info("Starting set PDU to OFF")
+        time.sleep(wait_time)
+        self.full_out_path = os.path.join(self.log_path, out_file_name)
+        cmd = "curl http://snmp:1234@{}/ons.cgi?led={}> {}".format(self.pdu_address,
+                                                                   self.pduon_address,
+                                                                   self.full_out_path)
+        self.pdu_process = utils.start_standing_subprocess(cmd)
+        wait_time = int("{}".format(self.pduon_wait_time))
+        self.log.info("Starting set PDU to ON for port1,"
+                      "wait for {}s".format(self.pduon_wait_time))
+        time.sleep(wait_time)
+        self.log.info("PDU setup complete")
+
+    def connect_to_wifi_network_and_run_iperf(self, network):
+        """Connection logic for open and psk wifi networks.
+
+        Logic steps are
+        1. Connect to the network.
+        2. Run iperf throghput.
+
+        Args:
+            params: A dictionary with network info.
+        """
+        self.connect_to_wifi_network(network)
+        self.iperf_test_func(network)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="0e4ad6ed-595c-4629-a4c9-c6be9c3c58e0")
+    def test_iot_connection_to_ASUS_RT_AC68U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="a76d8acc-808e-4a5d-a52b-5ba07d07b810")
+    def test_iot_connection_to_ASUS_RT_AC68U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="659a3e5e-07eb-4905-9cda-92e959c7b674")
+    def test_iot_connection_to_D_Link_DIR_868L_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="6bcfd736-30fc-48a8-b4fb-723d1d113f3c")
+    def test_iot_connection_to_D_Link_DIR_868L_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="c9da945a-2c4a-44e1-881d-adf307b39b21")
+    def test_iot_connection_to_TP_LINK_WR940N_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="db0d224d-df81-401f-bf35-08ad02e41a71")
+    def test_iot_connection_to_ASUS_RT_N66U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="845ff1d6-618d-40f3-81c3-6ed3a0751fde")
+    def test_iot_connection_to_ASUS_RT_N66U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="6908039b-ccc9-4777-a0f1-3494ce642014")
+    def test_iot_connection_to_ASUS_RT_AC54U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="2647c15f-2aad-47d7-8dee-b2ee1ac4cef6")
+    def test_iot_connection_to_ASUS_RT_AC54U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="99678f66-ddf1-454d-87e4-e55177ec380d")
+    def test_iot_connection_to_ASUS_RT_N56U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="4dd75e81-9a8e-44fd-9449-09f5ab8a63c3")
+    def test_iot_connection_to_ASUS_RT_N56U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="315397ce-50d5-4abf-a11c-1abcaef832d3")
+    def test_iot_connection_to_BELKIN_F9K1002v1_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="05ba464a-b1ef-4ac1-a32f-c919ec4aa1dd")
+    def test_iot_connection_to_CISCO_E1200_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="04912868-4a47-40ce-877e-4e4c89849557")
+    def test_iot_connection_to_TP_LINK_C2_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="53517a21-3802-4185-b8bb-6eaace063a42")
+    def test_iot_connection_to_TP_LINK_C2_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="71c08c1c-415d-4da4-a151-feef43fb6ad8")
+    def test_iot_connection_to_ASUS_RT_AC66U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="2322c155-07d1-47c9-bd21-2e358e3df6ee")
+    def test_iot_connection_to_ASUS_RT_AC66U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
diff --git a/acts_tests/tests/google/wifi/WifiIOTtpeTest.py b/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
new file mode 100644
index 0000000..3a00d0c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import time
+
+import acts.signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiIOTtpeTest(WifiBaseTest):
+    """ Tests for wifi IOT
+
+        Test Bed Requirement:
+          * One Android device
+          * Wi-Fi IOT networks visible to the device
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+
+        req_params = [ "iot_networks", ]
+        opt_params = [ "open_network", "iperf_server_address" ]
+        self.unpack_userparams(req_param_names=req_params,
+                               opt_param_names=opt_params)
+
+        asserts.assert_true(
+            len(self.iot_networks) > 0,
+            "Need at least one iot network with psk.")
+
+        if getattr(self, 'open_network', False):
+            self.iot_networks.append(self.open_network)
+
+        wutils.wifi_toggle_state(self.dut, True)
+        if "iperf_server_address" in self.user_params:
+            self.iperf_server = self.iperf_servers[0]
+            self.iperf_server.start()
+
+        # create hashmap for testcase name and SSIDs
+        self.iot_test_prefix = "test_iot_connection_to_"
+        self.ssid_map = {}
+        for network in self.iot_networks:
+            SSID = network['SSID'].replace('-','_')
+            self.ssid_map[SSID] = network
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        if "iperf_server_address" in self.user_params:
+            self.iperf_server.stop()
+
+    """Helper Functions"""
+
+    def connect_to_wifi_network(self, network):
+        """Connection logic for open and psk wifi networks.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        SSID = network[WifiEnums.SSID_KEY]
+        self.dut.ed.clear_all_events()
+        wutils.start_wifi_connection_scan(self.dut)
+        scan_results = self.dut.droid.wifiGetScanResults()
+        wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
+        wutils.wifi_connect(self.dut, network, num_of_tries=3)
+
+    def run_iperf_client(self, network):
+        """Run iperf traffic after connection.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        if "iperf_server_address" in self.user_params:
+            wait_time = 5
+            SSID = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic through {}".format(SSID))
+            time.sleep(wait_time)
+            port_arg = "-p {}".format(self.iperf_server.port)
+            success, data = self.dut.run_iperf_client(self.iperf_server_address,
+                                                      port_arg)
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+
+    def connect_to_wifi_network_and_run_iperf(self, network):
+        """Connection logic for open and psk wifi networks.
+
+        Logic steps are
+        1. Connect to the network.
+        2. Run iperf traffic.
+
+        Args:
+            params: A dictionary with network info.
+        """
+        self.connect_to_wifi_network(network)
+        self.run_iperf_client(network)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="0e4ad6ed-595c-4629-a4c9-c6be9c3c58e0")
+    def test_iot_connection_to_ASUS_RT_AC68U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="a76d8acc-808e-4a5d-a52b-5ba07d07b810")
+    def test_iot_connection_to_ASUS_RT_AC68U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="659a3e5e-07eb-4905-9cda-92e959c7b674")
+    def test_iot_connection_to_D_Link_DIR_868L_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="6bcfd736-30fc-48a8-b4fb-723d1d113f3c")
+    def test_iot_connection_to_D_Link_DIR_868L_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="c9da945a-2c4a-44e1-881d-adf307b39b21")
+    def test_iot_connection_to_TP_LINK_WR940N_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="db0d224d-df81-401f-bf35-08ad02e41a71")
+    def test_iot_connection_to_ASUS_RT_N66U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="845ff1d6-618d-40f3-81c3-6ed3a0751fde")
+    def test_iot_connection_to_ASUS_RT_N66U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="6908039b-ccc9-4777-a0f1-3494ce642014")
+    def test_iot_connection_to_ASUS_RT_AC54U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="2647c15f-2aad-47d7-8dee-b2ee1ac4cef6")
+    def test_iot_connection_to_ASUS_RT_AC54U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="99678f66-ddf1-454d-87e4-e55177ec380d")
+    def test_iot_connection_to_ASUS_RT_N56U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="4dd75e81-9a8e-44fd-9449-09f5ab8a63c3")
+    def test_iot_connection_to_ASUS_RT_N56U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="315397ce-50d5-4abf-a11c-1abcaef832d3")
+    def test_iot_connection_to_BELKIN_F9K1002v1_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="05ba464a-b1ef-4ac1-a32f-c919ec4aa1dd")
+    def test_iot_connection_to_CISCO_E1200_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="04912868-4a47-40ce-877e-4e4c89849557")
+    def test_iot_connection_to_TP_LINK_C2_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="53517a21-3802-4185-b8bb-6eaace063a42")
+    def test_iot_connection_to_TP_LINK_C2_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="71c08c1c-415d-4da4-a151-feef43fb6ad8")
+    def test_iot_connection_to_ASUS_RT_AC66U_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="2322c155-07d1-47c9-bd21-2e358e3df6ee")
+    def test_iot_connection_to_ASUS_RT_AC66U_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
diff --git a/acts_tests/tests/google/wifi/WifiLinkProbeTest.py b/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
new file mode 100644
index 0000000..de202b8
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+WifiEnums = wutils.WifiEnums
+NUM_LINK_PROBES = 8
+PROBE_DELAY_SEC = 3
+ATTENUATION = 40
+
+
+class WifiLinkProbeTest(WifiBaseTest):
+    """
+    Tests sending 802.11 Probe Request frames to the currently connected AP to
+    determine if the uplink to the AP is working.
+
+    Test Bed Requirements:
+    * One Android Device
+    * One Wi-Fi network visible to the device, with an attenuator
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        self.unpack_userparams(req_param_names=[],
+                               opt_param_names=["reference_networks"])
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True)
+
+        asserts.assert_true(len(self.reference_networks) > 0,
+                            "Need at least one reference network with psk.")
+        self.attenuators = wutils.group_attenuators(self.attenuators)
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut, True)
+        self.attenuators[0].set_atten(0)
+        self.attenuators[1].set_atten(0)
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+
+    # HELPER METHODS
+
+    def _test_link_probe_does_not_crash_device(self, network):
+        """
+        Connect to a network, send link probes, and verify that the device did
+        not crash. Also verify that at least one link probe succeeded.
+
+        Steps:
+        1. Connect to a network.
+        2. Send a few link probes.
+        3. Verify that at least one link probe succeeded.
+        4. Ensure that the device did not crash (by checking that it is
+           connected to the expected network).
+        """
+        wutils.wifi_connect(self.dut, network, num_of_tries=3)
+
+        results = wutils.send_link_probes(
+            self.dut, NUM_LINK_PROBES, PROBE_DELAY_SEC)
+
+        asserts.assert_true(any(result.is_success for result in results),
+                            "Expect at least 1 probe success: " + str(results))
+
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        expected = network[WifiEnums.SSID_KEY]
+        actual = wifi_info[WifiEnums.SSID_KEY]
+        asserts.assert_equal(
+            expected, actual,
+            "Device did not remain connected after sending link probes!")
+
+    def _test_link_probe_ap_attenuated(self, network):
+        """
+        Connect to a network, significantly attenuate the signal, and verify
+        that the device did not crash.
+
+        Steps:
+        1. Connect to a network.
+        2. Attenuate the signal.
+        3. Send a few link probes.
+        4. Stop attenuating the signal.
+        5. Ensure that the device did not crash (by checking that it is
+           connected to the expected network).
+        """
+        wutils.wifi_connect(self.dut, network, num_of_tries=3)
+        self.attenuators[0].set_atten(ATTENUATION)
+
+        wutils.send_link_probes(self.dut, NUM_LINK_PROBES, PROBE_DELAY_SEC)
+
+        # we cannot assert for failed link probe when attenuated, this would
+        # depend too much on the attenuator setup => too flaky
+
+        self.attenuators[0].set_atten(0)
+        time.sleep(PROBE_DELAY_SEC * 3)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        expected = network[WifiEnums.SSID_KEY]
+        actual = wifi_info[WifiEnums.SSID_KEY]
+        asserts.assert_equal(
+            expected, actual,
+            "Device did not remain connected after sending link probes!")
+
+    # TEST METHODS
+
+    @test_tracker_info(uuid='2afd309b-6bf3-4de4-9d8a-e4d35354a2cb')
+    def test_link_probe_does_not_crash_device_2g(self):
+        network = self.reference_networks[0]["2g"]
+        self._test_link_probe_does_not_crash_device(network)
+
+    @test_tracker_info(uuid='69417a6d-7090-4dd0-81ad-55fa3f12b7b1')
+    def test_link_probe_does_not_crash_device_5g(self):
+        network = self.reference_networks[0]["5g"]
+        self._test_link_probe_does_not_crash_device(network)
+
+    @test_tracker_info(uuid='54b8ffaa-c305-4772-928d-03342c51122d')
+    def test_link_probe_ap_attenuated_2g(self):
+        network = self.reference_networks[0]["2g"]
+        self._test_link_probe_ap_attenuated(network)
+
+    @test_tracker_info(uuid='54e29fa4-ff44-4aad-8999-676b361cacf4')
+    def test_link_probe_ap_attenuated_5g(self):
+        network = self.reference_networks[0]["5g"]
+        self._test_link_probe_ap_attenuated(network)
+
diff --git a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
new file mode 100644
index 0000000..f3b5102
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
@@ -0,0 +1,654 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import re
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from scapy.all import *
+from acts.controllers.ap_lib import hostapd_constants
+
+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
+SHORT_TIMEOUT = 5
+
+# Constants for WiFi state change operations.
+FORGET = 1
+TOGGLE = 2
+REBOOT_DUT = 3
+REBOOT_AP = 4
+
+# MAC Randomization setting constants.
+RANDOMIZATION_NONE = 0
+RANDOMIZATION_PERSISTENT = 1
+
+
+class WifiMacRandomizationTest(WifiBaseTest):
+    """Tests for APIs in Android's WifiManager class.
+
+    Test Bed Requirement:
+    * Atleast one Android device and atleast two Access Points.
+    * Several Wi-Fi networks visible to the device.
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_test_device_init(self.dut_client)
+        req_params = ["dbs_supported_models", "roaming_attn"]
+        opt_param = [
+            "open_network", "reference_networks", "wep_networks"
+        ]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if not hasattr(self, 'packet_capture'):
+            raise signals.TestFailure("Needs packet_capture attribute to "
+                                      "support sniffing.")
+        self.configure_packet_capture()
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(wep_network=True,
+                                               ap_count=2)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                wep_network=True,
+                                                mirror_ap=True,
+                                                ap_count=2)
+
+        asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+
+        # Reboot device to reset factory MAC of wlan1
+        self.dut.reboot()
+        self.dut_client.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_toggle_state(self.dut_client, True)
+        self.soft_ap_factory_mac = self.get_soft_ap_mac_address()
+        self.sta_factory_mac = self.dut.droid.wifigetFactorymacAddresses()[0]
+
+        self.wpapsk_2g = self.reference_networks[0]["2g"]
+        self.wpapsk_5g = self.reference_networks[0]["5g"]
+        self.wep_2g = self.wep_networks[0]["2g"]
+        self.wep_5g = self.wep_networks[0]["5g"]
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+            wutils.wifi_toggle_state(ad, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+        self.dut.droid.wifiRemoveNetworkSuggestions([])
+        wutils.reset_wifi(self.dut)
+        wutils.reset_wifi(self.dut_client)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+            del self.user_params["wep_networks"]
+
+
+    """Helper Functions"""
+
+
+    def get_randomized_mac(self, network):
+        """Get the randomized MAC address.
+
+        Args:
+            network: dict, network information.
+
+        Returns:
+            The randomized MAC address string for the network.
+
+        """
+        return self.dut.droid.wifigetRandomizedMacAddress(network)
+
+    def connect_to_network_and_verify_mac_randomization(self, network,
+            status=RANDOMIZATION_PERSISTENT):
+        """Connect to the given network and verify MAC.
+
+          Args:
+              network: dict, the network information.
+              status: int, MAC randomization level.
+
+          Returns:
+              The randomized MAC addresss string.
+
+        """
+        wutils.connect_to_wifi_network(self.dut, network)
+        return self.verify_mac_randomization(network, status=status)
+
+    def verify_mac_randomization_and_add_to_list(self, network, mac_list):
+        """Connect to a network and populate it's MAC in a reference list,
+        that will be used to verify any repeated MAC addresses.
+
+        Args:
+            network: dict, the network information.
+            mac_list: list of MAC addresss strings.
+
+        """
+        rand_mac = self.connect_to_network_and_verify_mac_randomization(
+                network)
+        if rand_mac in mac_list:
+            raise signals.TestFailure('A new Randomized MAC was not generated '
+                                      ' for this network %s.' % network)
+        mac_list.append(rand_mac)
+
+    def verify_mac_randomization(self, network, status=RANDOMIZATION_PERSISTENT):
+        """Get the various types of MAC addresses for the device and verify.
+
+        Args:
+            network: dict, the network information.
+            status: int, MAC randomization level.
+
+        Returns:
+            The randomized MAC address string for the network.
+
+        """
+        randomized_mac = self.get_randomized_mac(network)
+        default_mac = self.get_sta_mac_address()
+        self.log.info("Factory MAC = %s\nRandomized MAC = %s\nDefault MAC = %s" %
+              (self.sta_factory_mac, randomized_mac, default_mac))
+        message = ('Randomized MAC and Factory MAC are the same. '
+                   'Randomized MAC = %s, Factory MAC = %s' % (randomized_mac, self.sta_factory_mac))
+        asserts.assert_true(randomized_mac != self.sta_factory_mac, message)
+        if status == RANDOMIZATION_NONE:
+            asserts.assert_true(default_mac == self.sta_factory_mac, "Connection is not "
+                "using Factory MAC as the default MAC.")
+        else:
+            message = ('Connection is not using randomized MAC as the default MAC. '
+                       'Randomized MAC = %s, Deafult MAC = %s' % (randomized_mac, default_mac))
+            asserts.assert_true(default_mac == randomized_mac, message)
+        return randomized_mac
+
+    def check_mac_persistence(self, network, condition):
+        """Check if the MAC is persistent after carrying out specific operations
+        like forget WiFi, toggle WiFi, reboot device and AP.
+
+        Args:
+            network: dict, The network information.
+            condition: int, value to trigger certain  operation on the device.
+
+        Raises:
+            TestFaikure is the MAC is not persistent.
+
+        """
+        rand_mac1 = self.connect_to_network_and_verify_mac_randomization(network)
+
+        if condition == FORGET:
+            wutils.wifi_forget_network(self.dut, network['SSID'])
+
+        elif condition == TOGGLE:
+            wutils.wifi_toggle_state(self.dut, False)
+            wutils.wifi_toggle_state(self.dut, True)
+
+        elif condition == REBOOT_DUT:
+            self.dut.reboot()
+            time.sleep(DEFAULT_TIMEOUT)
+
+        elif condition == REBOOT_AP:
+            wutils.turn_ap_off(self, 1)
+            time.sleep(DEFAULT_TIMEOUT)
+            wutils.turn_ap_on(self, 1)
+            time.sleep(DEFAULT_TIMEOUT)
+
+        rand_mac2 = self.connect_to_network_and_verify_mac_randomization(network)
+
+        if rand_mac1 != rand_mac2:
+            raise signals.TestFailure('Randomized MAC is not persistent after '
+                                      'forgetting networ. Old MAC = %s New MAC'
+                                      ' = %s' % (rand_mac1, rand_mac2))
+
+    def verify_mac_not_found_in_pcap(self, mac, packets):
+        for pkt in packets:
+            self.log.debug("Packet Summary = %s" % pkt.summary())
+            if mac in pkt.summary():
+                raise signals.TestFailure("Caught Factory MAC in packet sniffer"
+                                          "Packet = %s Device = %s"
+                                           % (pkt.show(), self.dut))
+
+    def verify_mac_is_found_in_pcap(self, mac, packets):
+        for pkt in packets:
+            self.log.debug("Packet Summary = %s" % pkt.summary())
+            if mac in pkt.summary():
+                return
+        raise signals.TestFailure("Did not find MAC = %s in packet sniffer."
+                                  "for device %s" % (mac, self.dut))
+
+    def get_sta_mac_address(self):
+        """Gets the current MAC address being used for client mode."""
+        out = self.dut.adb.shell("ifconfig wlan0")
+        res = re.match(".* HWaddr (\S+).*", out, re.S)
+        return res.group(1)
+
+    def get_soft_ap_mac_address(self):
+        """Gets the current MAC address being used for SoftAp."""
+        if self.dut.model in self.dbs_supported_models:
+            out = self.dut.adb.shell("ifconfig wlan1")
+            return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
+        else:
+            return self.get_sta_mac_address()
+
+    def _add_suggestion_and_verify_mac_randomization(self, network_suggestion):
+        """Add wifi network suggestion and verify MAC randomization.
+
+        Args:
+            network_suggestion: network suggestion to add.
+
+        Returns:
+            Randomized MAC address.
+        """
+        self.log.info("Adding network suggestion")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion]),
+            "Failed to add suggestions")
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, network_suggestion[WifiEnums.SSID_KEY])
+        wutils.wait_for_connect(self.dut, network_suggestion[WifiEnums.SSID_KEY])
+        default_mac = self.get_sta_mac_address()
+        randomized_mac = self.dut.droid.wifiGetConnectionInfo()["mac_address"]
+        self.log.info("Factory MAC = %s\nRandomized MAC = %s\nDefault MAC = %s" %
+                      (self.sta_factory_mac, randomized_mac, default_mac))
+        asserts.assert_true(
+            default_mac == randomized_mac,
+            "Connection is not using randomized MAC as the default MAC.")
+        return randomized_mac
+
+    def _remove_suggestion_and_verify_disconnect(self, network_suggestion):
+        """Remove wifi network suggestion and verify device disconnects.
+
+        Args:
+            network_suggestion: network suggestion to remove.
+        """
+        self.dut.log.info("Removing network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiRemoveNetworkSuggestions([network_suggestion]),
+            "Failed to remove suggestions")
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.ed.clear_all_events()
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut,
+                network_suggestion[WifiEnums.SSID_KEY],
+                assert_on_fail=False),
+            "Device should not connect back")
+
+    """Tests"""
+
+
+    @test_tracker_info(uuid="2dd0a05e-a318-45a6-81cd-962e098fa242")
+    def test_set_mac_randomization_to_none(self):
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+        network = self.wpapsk_2g
+        # Set macRandomizationSetting to RANDOMIZATION_NONE.
+        network["macRand"] = RANDOMIZATION_NONE
+        self.connect_to_network_and_verify_mac_randomization(network,
+            status=RANDOMIZATION_NONE)
+        pcap_fname = '%s_%s.pcap' % \
+            (self.pcap_procs[hostapd_constants.BAND_2G][1],
+             hostapd_constants.BAND_2G.upper())
+        time.sleep(SHORT_TIMEOUT)
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        packets = rdpcap(pcap_fname)
+        self.verify_mac_is_found_in_pcap(self.sta_factory_mac, packets)
+
+    @test_tracker_info(uuid="d9e64202-02d5-421a-967c-42e45f1f7f91")
+    def test_mac_randomization_wpapsk(self):
+        """Verify MAC randomization for a WPA network.
+
+        Steps:
+            1. Connect to WPA network.
+            2. Get the Factory, Randomized and Default MACs.
+            3. Verify randomized MAC is the default MAC for the device.
+
+        """
+        self.connect_to_network_and_verify_mac_randomization(self.wpapsk_2g)
+
+    @test_tracker_info(uuid="b5be7c53-2edf-449e-ba70-a1fb7acf735e")
+    def test_mac_randomization_wep(self):
+        """Verify MAC randomization for a WEP network.
+
+        Steps:
+            1. Connect to WEP network.
+            2. Get the Factory, Randomized and Default MACs.
+            3. Verify randomized MAC is the default MAC for the device.
+
+        """
+        self.connect_to_network_and_verify_mac_randomization(self.wep_2g)
+
+    @test_tracker_info(uuid="f5347ac0-68d5-4882-a58d-1bd0d575503c")
+    def test_mac_randomization_open(self):
+        """Verify MAC randomization for a open network.
+
+        Steps:
+            1. Connect to open network.
+            2. Get the Factory, Randomized and Default MACs.
+            3. Verify randomized MAC is the default MAC for the device.
+
+        """
+        self.connect_to_network_and_verify_mac_randomization(self.open_2g)
+
+    @test_tracker_info(uuid="5d260421-2adf-4ace-b281-3d15aec39b2a")
+    def test_persistent_mac_after_forget(self):
+        """Check if MAC is persistent after forgetting/adding a network.
+
+        Steps:
+            1. Connect to WPA network and get the randomized MAC.
+            2. Forget the network.
+            3. Connect to the same network again.
+            4. Verify randomized MAC has not changed.
+
+        """
+        self.check_mac_persistence(self.wpapsk_2g, FORGET)
+
+    @test_tracker_info(uuid="09d40a93-ead2-45ca-9905-14b05fd79f34")
+    def test_persistent_mac_after_toggle(self):
+        """Check if MAC is persistent after toggling WiFi network.
+
+        Steps:
+            1. Connect to WPA network and get the randomized MAC.
+            2. Turn WiFi ON/OFF.
+            3. Connect to the same network again.
+            4. Verify randomized MAC has not changed.
+
+        """
+        self.check_mac_persistence(self.wpapsk_2g, TOGGLE)
+
+    @test_tracker_info(uuid="b3aa514f-8562-44e8-bfe0-4ecab9af165b")
+    def test_persistent_mac_after_device_reboot(self):
+        """Check if MAC is persistent after a device reboot.
+
+        Steps:
+            1. Connect to WPA network and get the randomized MAC.
+            2. Reboot DUT.
+            3. Connect to the same network again.
+            4. Verify randomized MAC has not changed.
+
+        """
+        self.check_mac_persistence(self.wpapsk_2g, REBOOT_DUT)
+
+    # Disable reboot test for debugging purpose.
+    #@test_tracker_info(uuid="82d691a0-22e4-4a3d-9596-e150531fcd34")
+    def persistent_mac_after_ap_reboot(self):
+        """Check if MAC is persistent after AP reboots itself.
+
+        Steps:
+            1. Connect to WPA network and get the randomized MAC.
+            2. Reboot AP(basically restart hostapd in our case).
+            3. Connect to the same network again.
+            4. Verify randomized MAC has not changed.
+
+        """
+        self.check_mac_persistence(self.wpapsk_2g, REBOOT_AP)
+
+    @test_tracker_info(uuid="e1f33dbc-808c-4e61-8a4a-3a72c1f63c7e")
+    def test_mac_randomization_multiple_networks(self):
+        """Connect to multiple networks and verify same MAC.
+
+        Steps:
+            1. Connect to network A, get randomizd MAC.
+            2. Conenct to network B, get randomized MAC.
+            3. Connect back to network A and verify same MAC.
+            4. Connect back to network B and verify same MAC.
+
+        """
+        mac_list = list()
+
+        # Connect to two different networks and get randomized MAC addresses.
+        self.verify_mac_randomization_and_add_to_list(self.wpapsk_2g, mac_list)
+        self.verify_mac_randomization_and_add_to_list(self.open_2g, mac_list)
+
+        # Connect to the previous network and check MAC is persistent.
+        mac_wpapsk = self.connect_to_network_and_verify_mac_randomization(
+                self.wpapsk_2g)
+        msg = ('Randomized MAC is not persistent for this network %s. Old MAC = '
+               '%s \nNew MAC = %s')
+        if mac_wpapsk != mac_list[0]:
+            raise signals.TestFailure(msg % (self.wpapsk_5g, mac_list[0], mac_wpapsk))
+        mac_open = self.connect_to_network_and_verify_mac_randomization(
+                self.open_2g)
+        if mac_open != mac_list[1]:
+            raise signals.TestFailure(msg %(self.open_5g, mac_list[1], mac_open))
+
+    @test_tracker_info(uuid="edb5a0e5-7f3b-4147-b1d3-48ad7ad9799e")
+    def test_mac_randomization_different_APs(self):
+        """Verify randomization using two different APs.
+
+        Steps:
+            1. Connect to network A on AP1, get the randomized MAC.
+            2. Connect to network B on AP2, get the randomized MAC.
+            3. Veirfy the two MACs are different.
+
+        """
+        ap1 = self.wpapsk_2g
+        ap2 = self.reference_networks[1]["5g"]
+        mac_ap1 = self.connect_to_network_and_verify_mac_randomization(ap1)
+        mac_ap2 = self.connect_to_network_and_verify_mac_randomization(ap2)
+        if mac_ap1 == mac_ap2:
+            raise signals.TestFailure("Same MAC address was generated for both "
+                                      "APs: %s" % mac_ap1)
+
+    @test_tracker_info(uuid="b815e9ce-bccd-4fc3-9774-1e1bc123a2a8")
+    def test_mac_randomization_ap_sta(self):
+        """Bring up STA and softAP and verify MAC randomization.
+
+        Steps:
+            1. Connect to a network and get randomized MAC.
+            2. Bring up softAP on the DUT.
+            3. Connect to softAP network on the client and get MAC.
+            4. Verify AP and STA use different randomized MACs.
+            5. Find the channel of the SoftAp network.
+            6. Configure sniffer on that channel.
+            7. Verify the factory MAC is not leaked.
+
+        """
+        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
+        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
+        mac_sta = self.connect_to_network_and_verify_mac_randomization(
+                self.wpapsk_2g)
+        softap = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_2G)
+        wutils.connect_to_wifi_network(self.dut_client, softap)
+        softap_info = self.dut_client.droid.wifiGetConnectionInfo()
+        mac_ap = softap_info['mac_address']
+        if mac_sta == mac_ap:
+            raise signals.TestFailure("Same MAC address was used for both "
+                                      "AP and STA: %s" % mac_sta)
+
+        # Verify SoftAp MAC is randomized
+        softap_mac = self.get_soft_ap_mac_address()
+        message = ('Randomized SoftAp MAC and Factory SoftAp MAC are the same. '
+                   'Randomized SoftAp MAC = %s, Factory SoftAp MAC = %s'
+                   % (softap_mac, self.soft_ap_factory_mac))
+        asserts.assert_true(softap_mac != self.soft_ap_factory_mac, message)
+
+        softap_channel = hostapd_constants.CHANNEL_MAP[softap_info['frequency']]
+        self.log.info("softap_channel = %s\n" % (softap_channel))
+        result = self.packet_capture.configure_monitor_mode(
+            hostapd_constants.BAND_2G, softap_channel)
+        if not result:
+            raise ValueError("Failed to configure channel for 2G band")
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+        # re-connect to the softAp network after sniffer is started
+        wutils.connect_to_wifi_network(self.dut_client, self.wpapsk_2g)
+        wutils.connect_to_wifi_network(self.dut_client, softap)
+        time.sleep(SHORT_TIMEOUT)
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        pcap_fname = '%s_%s.pcap' % \
+            (self.pcap_procs[hostapd_constants.BAND_2G][1],
+             hostapd_constants.BAND_2G.upper())
+        packets = rdpcap(pcap_fname)
+        self.verify_mac_not_found_in_pcap(self.soft_ap_factory_mac, packets)
+        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets)
+        self.verify_mac_is_found_in_pcap(softap_mac, packets)
+        self.verify_mac_is_found_in_pcap(self.get_sta_mac_address(), packets)
+
+    @test_tracker_info(uuid="3ca3f911-29f1-41fb-b836-4d25eac1669f")
+    def test_roaming_mac_randomization(self):
+        """test MAC randomization in the roaming scenario.
+
+        Steps:
+            1. Connect to network A on AP1, get randomized MAC.
+            2. Set AP1 to MAX attenuation so that we roam to AP2.
+            3. Wait for device to roam to AP2 and get randomized MAC.
+            4. Veirfy that the device uses same AMC for both APs.
+
+        """
+        AP1_network = self.reference_networks[0]["5g"]
+        AP2_network = self.reference_networks[1]["5g"]
+        if "OpenWrtAP" in self.user_params:
+            AP1_network["bssid"] = self.bssid_map[0]["5g"][AP1_network["SSID"]]
+            AP2_network["bssid"] = self.bssid_map[1]["5g"][AP2_network["SSID"]]
+        wutils.set_attns(self.attenuators, "AP1_on_AP2_off", self.roaming_attn)
+        mac_before_roam = self.connect_to_network_and_verify_mac_randomization(
+                AP1_network)
+        wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
+                "AP1_off_AP2_on", AP2_network, self.roaming_attn)
+        mac_after_roam = self.get_randomized_mac(AP2_network)
+        if mac_after_roam != mac_before_roam:
+            raise signals.TestFailure("Randomized MAC address changed after "
+                   "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after "
+                   "roam = %s" %(mac_before_roam, mac_after_roam))
+        wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
+                "AP1_on_AP2_off", AP1_network, self.roaming_attn)
+        mac_after_roam = self.get_randomized_mac(AP1_network)
+        if mac_after_roam != mac_before_roam:
+            raise signals.TestFailure("Randomized MAC address changed after "
+                   "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after "
+                   "roam = %s" %(mac_before_roam, mac_after_roam))
+
+    @test_tracker_info(uuid="17b12f1a-7c62-4188-b5a5-52d7a0bb7849")
+    def test_check_mac_sta_with_link_probe(self):
+        """Test to ensure Factory MAC is not exposed, using sniffer data.
+
+        Steps:
+            1. Configure and start the sniffer on 5GHz band.
+            2. Connect to 5GHz network.
+            3. Send link probes.
+            4. Stop the sniffer.
+            5. Invoke scapy to read the .pcap file.
+            6. Read each packet summary and make sure Factory MAC is not used.
+
+        """
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+        time.sleep(SHORT_TIMEOUT)
+        network = self.wpapsk_5g
+        rand_mac = self.connect_to_network_and_verify_mac_randomization(network)
+        wutils.send_link_probes(self.dut, 3, 3)
+        pcap_fname = '%s_%s.pcap' % \
+            (self.pcap_procs[hostapd_constants.BAND_5G][1],
+             hostapd_constants.BAND_5G.upper())
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        time.sleep(SHORT_TIMEOUT)
+        packets = rdpcap(pcap_fname)
+        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets)
+        self.verify_mac_is_found_in_pcap(self.get_sta_mac_address(), packets)
+
+    @test_tracker_info(uuid="1c2cc0fd-a340-40c4-b679-6acc5f526451")
+    def test_check_mac_in_wifi_scan(self):
+        """Test to ensure Factory MAC is not exposed, in Wi-Fi scans
+
+        Steps:
+          1. Configure and start the sniffer on both bands.
+          2. Perform a full scan.
+          3. Stop the sniffer.
+          4. Invoke scapy to read the .pcap file.
+          5. Read each packet summary and make sure Factory MAC is not used.
+
+        """
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+        wutils.start_wifi_connection_scan(self.dut)
+        time.sleep(SHORT_TIMEOUT)
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        pcap_fname = '%s_%s.pcap' % \
+            (self.pcap_procs[hostapd_constants.BAND_2G][1],
+             hostapd_constants.BAND_2G.upper())
+        packets = rdpcap(pcap_fname)
+        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets)
+
+    @test_tracker_info(uuid="7714d31f-bb08-4f29-b246-0ce1398a3c03")
+    def test_mac_randomization_for_network_suggestion(self):
+        """Add network suggestion and verify MAC randomization.
+
+        Steps:
+            1. Add a network suggestion and verify device connects to it.
+            2. Verify the device uses randomized MAC address for this network.
+        """
+        network_suggestion = self.reference_networks[0]["5g"]
+        self._add_suggestion_and_verify_mac_randomization(network_suggestion)
+
+    @test_tracker_info(uuid="144ad0b4-b79d-4b1d-a8a9-3c612a76c32c")
+    def test_enhanced_mac_randomization_for_network_suggestion(self):
+        """Test enhanced MAC randomization.
+
+        Steps:
+            1. Add a network suggestion with enhanced mac randomization enabled.
+            2. Connect to the network and verify the MAC address is random.
+            3. Remove the suggestion network and add it back.
+            4. Connect to the network. Verify the MAC address is random and
+               different from the randomized MAC observed in step 2.
+        """
+        network_suggestion = self.reference_networks[0]["5g"]
+        network_suggestion["enhancedMacRandomizationEnabled"] = True
+
+        # add network suggestion with enhanced mac randomization
+        randomized_mac1 = self._add_suggestion_and_verify_mac_randomization(
+            network_suggestion)
+
+        # remove network suggestion and verify no connection
+        self._remove_suggestion_and_verify_disconnect(network_suggestion)
+
+        # add network suggestion and verify device connects back
+        randomized_mac2 = self._add_suggestion_and_verify_mac_randomization(
+            network_suggestion)
+
+        # verify both randomized mac addrs are different
+        asserts.assert_true(randomized_mac1 != randomized_mac2,
+                            "Randomized MAC addresses are same.")
+
diff --git a/acts_tests/tests/google/wifi/WifiManagerTest.py b/acts_tests/tests/google/wifi/WifiManagerTest.py
new file mode 100644
index 0000000..8b4c197
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiManagerTest.py
@@ -0,0 +1,1039 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+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(WifiBaseTest):
+    """Tests for APIs in Android's WifiManager class.
+
+    Test Bed Requirement:
+    * Two Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_toggle_state(self.dut, True)
+
+        self.dut_client = None
+        if len(self.android_devices) > 1:
+            self.dut_client = self.android_devices[1]
+            wutils.wifi_test_device_init(self.dut_client)
+            wutils.wifi_toggle_state(self.dut_client, True)
+
+        req_params = []
+        opt_param = [
+            "open_network", "reference_networks", "iperf_server_address",
+            "wpa_networks", "wep_networks", "iperf_server_port"
+        ]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(wpa_network=True, wep_network=True)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                wep_network=True)
+
+        asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+        self.wpapsk_2g = self.reference_networks[0]["2g"]
+        self.wpapsk_5g = self.reference_networks[0]["5g"]
+        self.open_network_2g = self.open_network[0]["2g"]
+        self.open_network_5g = self.open_network[0]["5g"]
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+        wutils.wifi_toggle_state(self.dut, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+        self.turn_location_off_and_scan_toggle_off()
+        if self.dut.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.dut)
+        wutils.reset_wifi(self.dut)
+        if self.dut_client:
+            wutils.reset_wifi(self.dut_client)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+
+    def connect_to_wifi_network(self, params):
+        """Connection logic for open and psk wifi networks.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+        """
+        network, ad = params
+        droid = ad.droid
+        ed = ad.ed
+        SSID = network[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            ad, SSID);
+        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 get_enabled_network(self, network1, network2):
+        """Check network status and return currently unconnected network.
+
+        Args:
+            network1: dict representing a network.
+            network2: dict representing a network.
+
+        Return:
+            Network dict of the unconnected network.
+
+        """
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        enabled = network1
+        if wifi_info[WifiEnums.SSID_KEY] == network1[WifiEnums.SSID_KEY]:
+            enabled = network2
+        return enabled
+
+    def check_configstore_networks(self, networks):
+        """Verify that all previously configured networks are presistent after
+           reboot.
+
+        Args:
+            networks: List of network dicts.
+
+        Return:
+            None. Raises TestFailure.
+
+        """
+        network_info = self.dut.droid.wifiGetConfiguredNetworks()
+        if len(network_info) != len(networks):
+            msg = (
+                "Length of configured networks before and after reboot don't"
+                " match. \nBefore reboot = %s \n After reboot = %s" %
+                (networks, network_info))
+            raise signals.TestFailure(msg)
+        current_count = 0
+        # For each network, check if it exists in configured list after reboot
+        for network in networks:
+            exists = wutils.match_networks({
+                WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]
+            }, network_info)
+            if not len(exists):
+                raise signals.TestFailure("%s network is not present in the"
+                                          " configured list after reboot" %
+                                          network[WifiEnums.SSID_KEY])
+            # Get the new network id for each network after reboot.
+            network[WifiEnums.NETID_KEY] = exists[0]['networkId']
+            if exists[0]['status'] == 'CURRENT':
+                current_count += 1
+                # At any given point, there can only be one currently active
+                # network, defined with 'status':'CURRENT'
+                if current_count > 1:
+                    raise signals.TestFailure("More than one network showing"
+                                              "as 'CURRENT' after reboot")
+
+    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.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, network_ssid);
+        wutils.wifi_connect_by_id(self.dut, network_id)
+        connect_data = self.dut.droid.wifiGetConnectionInfo()
+        connect_ssid = connect_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.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+        """
+        if "iperf_server_address" in self.user_params:
+            wait_time = 5
+            network, ad = params
+            SSID = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic through {}".format(SSID))
+            time.sleep(wait_time)
+            port_arg = "-p {}".format(self.iperf_server_port)
+            success, data = ad.run_iperf_client(self.iperf_server_address,
+                                                port_arg)
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+
+    def connect_to_wifi_network_toggle_wifi_and_run_iperf(self, params):
+        """ Connect to the provided network and then toggle wifi mode and wait
+        for reconnection to the provided network.
+
+        Logic steps are
+        1. Connect to the network.
+        2. Turn wifi off.
+        3. Turn wifi on.
+        4. Wait for connection to the network.
+        5. Run iperf traffic.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+       """
+        network, ad = params
+        self.connect_to_wifi_network(params)
+        wutils.toggle_wifi_and_wait_for_reconnection(
+            ad, network, num_of_tries=5)
+        self.run_iperf_client(params)
+
+    def run_iperf(self, iperf_args):
+        if "iperf_server_address" not in self.user_params:
+            self.log.error(("Missing iperf_server_address. "
+                            "Provide one in config."))
+        else:
+            iperf_addr = self.user_params["iperf_server_address"]
+            self.log.info("Running iperf client.")
+            result, data = self.dut.run_iperf_client(iperf_addr, iperf_args)
+            self.log.debug(data)
+
+    def run_iperf_rx_tx(self, time, omit=10):
+        args = "-p {} -t {} -O 10".format(self.iperf_server_port, time, omit)
+        self.log.info("Running iperf client {}".format(args))
+        self.run_iperf(args)
+        args = "-p {} -t {} -O 10 -R".format(self.iperf_server_port, time,
+                                             omit)
+        self.log.info("Running iperf client {}".format(args))
+        self.run_iperf(args)
+
+    def get_energy_info(self):
+        """ Steps:
+            1. Check that the WiFi energy info reporting support on this device
+               is as expected (support or not).
+            2. If the device does not support energy info reporting as
+               expected, skip the test.
+            3. Call API to get WiFi energy info.
+            4. Verify the values of "ControllerEnergyUsed" and
+               "ControllerIdleTimeMillis" in energy info don't decrease.
+            5. Repeat from Step 3 for 10 times.
+        """
+        # Check if dut supports energy info reporting.
+        actual_support = self.dut.droid.wifiIsEnhancedPowerReportingSupported()
+        model = self.dut.model
+        if not actual_support:
+            asserts.skip(
+                ("Device %s does not support energy info reporting as "
+                 "expected.") % model)
+        # Verify reported values don't decrease.
+        self.log.info(("Device %s supports energy info reporting, verify that "
+                       "the reported values don't decrease.") % model)
+        energy = 0
+        idle_time = 0
+        for i in range(10):
+            info = self.dut.droid.wifiGetControllerActivityEnergyInfo()
+            self.log.debug("Iteration %d, got energy info: %s" % (i, info))
+            new_energy = info["ControllerEnergyUsed"]
+            new_idle_time = info["ControllerIdleTimeMillis"]
+            asserts.assert_true(new_energy >= energy,
+                                "Energy value decreased: previous %d, now %d" %
+                                (energy, new_energy))
+            energy = new_energy
+            asserts.assert_true(new_idle_time >= idle_time,
+                                "Idle time decreased: previous %d, now %d" % (
+                                    idle_time, new_idle_time))
+            idle_time = new_idle_time
+            wutils.start_wifi_connection_scan(self.dut)
+
+    def turn_location_on_and_scan_toggle_on(self):
+        """ Turns on wifi location scans.
+        """
+        acts.utils.set_location_service(self.dut, True)
+        self.dut.droid.wifiScannerToggleAlwaysAvailable(True)
+        msg = "Failed to turn on location service's scan."
+        asserts.assert_true(self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
+
+    def turn_location_off_and_scan_toggle_off(self):
+        """ Turns off wifi location scans.
+        """
+        acts.utils.set_location_service(self.dut, False)
+        self.dut.droid.wifiScannerToggleAlwaysAvailable(False)
+        msg = "Failed to turn off location service's scan."
+        asserts.assert_true(not self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
+
+    def turn_location_on_and_scan_toggle_off(self):
+        """ Turns off wifi location scans, but keeps location on.
+        """
+        acts.utils.set_location_service(self.dut, True)
+        self.dut.droid.wifiScannerToggleAlwaysAvailable(False)
+        msg = "Failed to turn off location service's scan."
+        asserts.assert_true(not self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
+
+    def helper_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(self.dut)
+        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 helper_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(self.dut)
+        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.")
+
+    def helper_reboot_configstore_reconnect(self):
+        """Connect to multiple networks, reboot then reconnect to previously
+           connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Reboot device.
+        4. Verify all networks are persistent after reboot.
+        5. Reconnect to the non-current network.
+
+        """
+        network_list = self.connect_multiple_networks(self.dut)
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        self.check_configstore_networks(network_list)
+
+        reconnect_to = self.get_enabled_network(network_list[BAND_2GHZ],
+                                                network_list[BAND_5GHZ])
+
+        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 failed to reconnect to the correct"
+                " network after reboot.")
+
+    def helper_toggle_wifi_reboot_configstore_reconnect(self):
+        """Connect to multiple networks, disable WiFi, reboot, then
+           reconnect to previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Turn WiFi OFF.
+        4. Reboot device.
+        5. Turn WiFi ON.
+        4. Verify all networks are persistent after reboot.
+        5. Reconnect to the non-current network.
+
+        """
+        network_list = self.connect_multiple_networks(self.dut)
+        self.log.debug("Toggling wifi OFF")
+        wutils.wifi_toggle_state(self.dut, False)
+        time.sleep(DEFAULT_TIMEOUT)
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        self.log.debug("Toggling wifi ON")
+        wutils.wifi_toggle_state(self.dut, True)
+        time.sleep(DEFAULT_TIMEOUT)
+        self.check_configstore_networks(network_list)
+        reconnect_to = self.get_enabled_network(network_list[BAND_2GHZ],
+                                                network_list[BAND_5GHZ])
+        reconnect = self.connect_to_wifi_network_with_id(
+            reconnect_to[WifiEnums.NETID_KEY],
+            reconnect_to[WifiEnums.SSID_KEY])
+        if not reconnect:
+            msg = ("Device failed to reconnect to the correct network after"
+                   " toggling WiFi and rebooting.")
+            raise signals.TestFailure(msg)
+
+    def helper_toggle_airplane_reboot_configstore_reconnect(self):
+        """Connect to multiple networks, enable Airplane mode, reboot, then
+           reconnect to previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Toggle Airplane mode ON.
+        4. Reboot device.
+        5. Toggle Airplane mode OFF.
+        4. Verify all networks are persistent after reboot.
+        5. Reconnect to the non-current network.
+
+        """
+        network_list = self.connect_multiple_networks(self.dut)
+        self.log.debug("Toggling Airplane mode ON")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, True),
+            "Can not turn on airplane mode on: %s" % self.dut.serial)
+        time.sleep(DEFAULT_TIMEOUT)
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        self.log.debug("Toggling Airplane mode OFF")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, False),
+            "Can not turn on airplane mode on: %s" % self.dut.serial)
+        time.sleep(DEFAULT_TIMEOUT)
+        self.check_configstore_networks(network_list)
+        reconnect_to = self.get_enabled_network(network_list[BAND_2GHZ],
+                                                network_list[BAND_5GHZ])
+        reconnect = self.connect_to_wifi_network_with_id(
+            reconnect_to[WifiEnums.NETID_KEY],
+            reconnect_to[WifiEnums.SSID_KEY])
+        if not reconnect:
+            msg = ("Device failed to reconnect to the correct network after"
+                   " toggling Airplane mode and rebooting.")
+            raise signals.TestFailure(msg)
+
+    def verify_traffic_between_devices(self,dest_device,src_device,num_of_tries=2):
+        """Test the clients and DUT can ping each other.
+
+        Args:
+            num_of_tries: the retry times of ping test.
+            dest_device:Test device.
+            src_device:Second DUT access same AP
+        """
+        dest_device = dest_device.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        for _ in range(num_of_tries):
+            if acts.utils.adb_shell_ping(src_device, count=10, dest_ip=dest_device, timeout=20):
+                break
+        else:
+            asserts.fail("Ping to %s from %s failed" % (src_device.serial, dest_device))
+
+    """Tests"""
+
+    @test_tracker_info(uuid="525fc5e3-afba-4bfd-9a02-5834119e3c66")
+    def test_toggle_wifi_state_and_get_startupTime(self):
+        """Test toggling wifi"""
+        self.log.debug("Going from on to off.")
+        wutils.wifi_toggle_state(self.dut, False)
+        self.log.debug("Going from off to on.")
+        startTime = time.time()
+        wutils.wifi_toggle_state(self.dut, True)
+        startup_time = time.time() - startTime
+        self.log.debug("WiFi was enabled on the device in %s s." % startup_time)
+
+    @test_tracker_info(uuid="e9d11563-2bbe-4c96-87eb-ec919b51435b")
+    def test_toggle_with_screen(self):
+        """Test toggling wifi with screen on/off"""
+        wait_time = 5
+        self.log.debug("Screen from off to on.")
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        time.sleep(wait_time)
+        self.log.debug("Going from on to off.")
+        try:
+            wutils.wifi_toggle_state(self.dut, False)
+            time.sleep(wait_time)
+            self.log.debug("Going from off to on.")
+            wutils.wifi_toggle_state(self.dut, True)
+        finally:
+            self.dut.droid.wakeLockRelease()
+            time.sleep(wait_time)
+            self.dut.droid.goToSleepNow()
+
+    @test_tracker_info(uuid="71556e06-7fb1-4e2b-9338-b01f1f8e286e")
+    def test_scan(self):
+        """Test wifi connection scan can start and find expected networks."""
+        ssid = self.open_network_2g[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, ssid)
+        ssid = self.open_network_5g[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, ssid)
+
+    @test_tracker_info(uuid="3ea09efb-6921-429e-afb1-705ef5a09afa")
+    def test_scan_with_wifi_off_and_location_scan_on(self):
+        """Put wifi in scan only mode"""
+        self.turn_location_on_and_scan_toggle_on()
+        wutils.wifi_toggle_state(self.dut, False)
+
+        """Test wifi connection scan can start and find expected networks."""
+        ssid = self.open_network_2g[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, ssid)
+        ssid = self.open_network_5g[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, ssid)
+
+    @test_tracker_info(uuid="558652de-c802-405f-b9dc-b7fcc9237673")
+    def test_scan_after_reboot_with_wifi_off_and_location_scan_on(self):
+        """Put wifi in scan only mode"""
+        self.turn_location_on_and_scan_toggle_on()
+        wutils.wifi_toggle_state(self.dut, False)
+
+        # Reboot the device.
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+
+        """Test wifi connection scan can start and find expected networks."""
+        ssid = self.open_network_2g[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, ssid)
+        ssid = self.open_network_5g[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, ssid)
+
+    @test_tracker_info(uuid="770caebe-bcb1-43ac-95b6-5dd52dd90e80")
+    def test_scan_with_wifi_off_and_location_scan_off(self):
+        """Turn off wifi and location scan"""
+        self.turn_location_on_and_scan_toggle_off()
+        wutils.wifi_toggle_state(self.dut, False)
+
+        """Test wifi connection scan should fail."""
+        self.dut.droid.wifiStartScan()
+        try:
+            self.dut.ed.pop_event("WifiManagerScanResultsAvailable", 60)
+        except queue.Empty:
+            self.log.debug("Wifi scan results not received.")
+        else:
+            asserts.fail("Wi-Fi scan results received")
+
+    @test_tracker_info(uuid="a4ad9930-a8fa-4868-81ed-a79c7483e502")
+    def test_add_network(self):
+        """Test wifi connection scan."""
+        ssid = self.open_network_2g[WifiEnums.SSID_KEY]
+        nId = self.dut.droid.wifiAddNetwork(self.open_network_2g)
+        asserts.assert_true(nId > -1, "Failed to add network.")
+        configured_networks = self.dut.droid.wifiGetConfiguredNetworks()
+        self.log.debug(
+            ("Configured networks after adding: %s" % configured_networks))
+        wutils.assert_network_in_list({
+            WifiEnums.SSID_KEY: ssid
+        }, configured_networks)
+
+    @test_tracker_info(uuid="aca85551-10ba-4007-90d9-08bcdeb16a60")
+    def test_forget_network(self):
+        ssid = self.open_network_2g[WifiEnums.SSID_KEY]
+        nId = self.dut.droid.wifiAddNetwork(self.open_network_2g)
+        asserts.assert_true(nId > -1, "Failed to add network.")
+        configured_networks = self.dut.droid.wifiGetConfiguredNetworks()
+        self.log.debug(
+            ("Configured networks after adding: %s" % configured_networks))
+        wutils.assert_network_in_list({
+            WifiEnums.SSID_KEY: ssid
+        }, configured_networks)
+        wutils.wifi_forget_network(self.dut, ssid)
+        configured_networks = self.dut.droid.wifiGetConfiguredNetworks()
+        for nw in configured_networks:
+            asserts.assert_true(
+                nw[WifiEnums.BSSID_KEY] != ssid,
+                "Found forgotten network %s in configured networks." % ssid)
+
+    @test_tracker_info(uuid="3cff17f6-b684-4a95-a438-8272c2ad441d")
+    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.")
+
+    @test_tracker_info(uuid="334175c3-d26a-4c87-a8ab-8eb220b2d80f")
+    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.
+
+        """
+        self.helper_reconnect_toggle_wifi()
+
+    @test_tracker_info(uuid="bd2cec9e-7f17-44ef-8a0c-4da92a9b55ae")
+    def test_reconnect_toggle_wifi_with_location_scan_on(self):
+        """Connect to multiple networks, turn off/on wifi, then reconnect to
+           a previously connected network.
+
+        Steps:
+        1. Turn on location scans.
+        2. Connect to a 2GHz network.
+        3. Connect to a 5GHz network.
+        4. Turn WiFi OFF/ON.
+        5. Reconnect to the non-current network.
+
+        """
+        self.turn_location_on_and_scan_toggle_on()
+        self.helper_reconnect_toggle_wifi()
+
+    @test_tracker_info(uuid="8e6e6c21-fefb-4fe8-9fb1-f09b1182b76d")
+    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.
+
+        """
+        self.helper_reconnect_toggle_airplane()
+
+    @test_tracker_info(uuid="28562f13-8a0a-492e-932c-e587560db5f2")
+    def test_reconnect_toggle_airplane_with_location_scan_on(self):
+        """Connect to multiple networks, turn on/off Airplane moce, then
+           reconnect a previously connected network.
+
+        Steps:
+        1. Turn on location scans.
+        2. Connect to a 2GHz network.
+        3. Connect to a 5GHz network.
+        4. Turn ON/OFF Airplane mode.
+        5. Reconnect to the non-current network.
+
+        """
+        self.turn_location_on_and_scan_toggle_on()
+        self.helper_reconnect_toggle_airplane()
+
+    @test_tracker_info(uuid="3d041c12-05e2-46a7-ab9b-e3f60cc735db")
+    def test_reboot_configstore_reconnect(self):
+        """Connect to multiple networks, reboot then reconnect to previously
+           connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Reboot device.
+        4. Verify all networks are persistent after reboot.
+        5. Reconnect to the non-current network.
+
+        """
+        self.helper_reboot_configstore_reconnect()
+
+    @test_tracker_info(uuid="a70d5853-67b5-4d48-bdf7-08ee51fafd21")
+    def test_reboot_configstore_reconnect_with_location_scan_on(self):
+        """Connect to multiple networks, reboot then reconnect to previously
+           connected network.
+
+        Steps:
+        1. Turn on location scans.
+        2. Connect to a 2GHz network.
+        3. Connect to a 5GHz network.
+        4. Reboot device.
+        5. Verify all networks are persistent after reboot.
+        6. Reconnect to the non-current network.
+
+        """
+        self.turn_location_on_and_scan_toggle_on()
+        self.helper_reboot_configstore_reconnect()
+
+    @test_tracker_info(uuid="26d94dfa-1349-4c8b-aea0-475eb73bb521")
+    def test_toggle_wifi_reboot_configstore_reconnect(self):
+        """Connect to multiple networks, disable WiFi, reboot, then
+           reconnect to previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Turn WiFi OFF.
+        4. Reboot device.
+        5. Turn WiFi ON.
+        4. Verify all networks are persistent after reboot.
+        5. Reconnect to the non-current network.
+
+        """
+        self.helper_toggle_wifi_reboot_configstore_reconnect()
+
+    @test_tracker_info(uuid="7c004a3b-c1c6-4371-9124-0f34650be915")
+    def test_toggle_wifi_reboot_configstore_reconnect_with_location_scan_on(self):
+        """Connect to multiple networks, disable WiFi, reboot, then
+           reconnect to previously connected network.
+
+        Steps:
+        1. Turn on location scans.
+        2. Connect to a 2GHz network.
+        3. Connect to a 5GHz network.
+        4. Turn WiFi OFF.
+        5. Reboot device.
+        6. Turn WiFi ON.
+        7. Verify all networks are persistent after reboot.
+        8. Reconnect to the non-current network.
+
+        """
+        self.turn_location_on_and_scan_toggle_on()
+        self.helper_toggle_wifi_reboot_configstore_reconnect()
+
+    @test_tracker_info(uuid="4fce017b-b443-40dc-a598-51d59d3bb38f")
+    def test_toggle_airplane_reboot_configstore_reconnect(self):
+        """Connect to multiple networks, enable Airplane mode, reboot, then
+           reconnect to previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Toggle Airplane mode ON.
+        4. Reboot device.
+        5. Toggle Airplane mode OFF.
+        4. Verify all networks are persistent after reboot.
+        5. Reconnect to the non-current network.
+
+        """
+        self.helper_toggle_airplane_reboot_configstore_reconnect()
+
+    @test_tracker_info(uuid="7f0810f9-2338-4158-95f5-057f5a1905b6")
+    def test_toggle_airplane_reboot_configstore_reconnect_with_location_scan_on(self):
+        """Connect to multiple networks, enable Airplane mode, reboot, then
+           reconnect to previously connected network.
+
+        Steps:
+        1. Turn on location scans.
+        2. Connect to a 2GHz network.
+        3. Connect to a 5GHz network.
+        4. Toggle Airplane mode ON.
+        5. Reboot device.
+        6. Toggle Airplane mode OFF.
+        7. Verify all networks are persistent after reboot.
+        8. Reconnect to the non-current network.
+
+        """
+        self.turn_location_on_and_scan_toggle_on()
+        self.helper_toggle_airplane_reboot_configstore_reconnect()
+
+    @test_tracker_info(uuid="81eb7527-4c92-4422-897a-6b5f6445e84a")
+    def test_config_store_with_wpapsk_2g(self):
+        self.connect_to_wifi_network_toggle_wifi_and_run_iperf(
+            (self.wpapsk_2g, self.dut))
+
+    @test_tracker_info(uuid="8457903d-cb7e-4c89-bcea-7f59585ea6e0")
+    def test_config_store_with_wpapsk_5g(self):
+        self.connect_to_wifi_network_toggle_wifi_and_run_iperf(
+            (self.wpapsk_5g, self.dut))
+
+    @test_tracker_info(uuid="b9fbc13a-47b4-4f64-bd2c-e5a3cb24ab2f")
+    def test_tdls_supported(self):
+        model = self.dut.model
+        self.log.debug("Model is %s" % model)
+        if not model.startswith("volantis"):
+            asserts.assert_true(self.dut.droid.wifiIsTdlsSupported(), (
+                "TDLS should be supported on %s, but device is "
+                "reporting not supported.") % model)
+        else:
+            asserts.assert_false(self.dut.droid.wifiIsTdlsSupported(), (
+                "TDLS should not be supported on %s, but device "
+                "is reporting supported.") % model)
+
+    @test_tracker_info(uuid="50637d40-ea59-4f4b-9fc1-e6641d64074c")
+    def test_energy_info(self):
+        """Verify the WiFi energy info reporting feature """
+        self.get_energy_info()
+
+    @test_tracker_info(uuid="1f1cf549-53eb-4f36-9f33-ce06c9158efc")
+    def test_energy_info_connected(self):
+        """Verify the WiFi energy info reporting feature when connected.
+
+        Connect to a wifi network, then the same as test_energy_info.
+        """
+        wutils.wifi_connect(self.dut, self.open_network_2g)
+        self.get_energy_info()
+
+    @test_tracker_info(uuid="2622c253-defc-4a35-93a6-ca9d29a8238c")
+    def test_connect_to_wep_2g(self):
+        """Verify DUT can connect to 2GHz WEP network
+
+        Steps:
+        1. Ensure the 2GHz WEP network is visible in scan result.
+        2. Connect to the network and validate internet connection.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wep_networks[0]["2g"])
+
+    @test_tracker_info(uuid="1f2d17a2-e92d-43af-966b-3421c0db8620")
+    def test_connect_to_wep_5g(self):
+        """Verify DUT can connect to 5GHz WEP network
+
+        Steps:
+        1. Ensure the 5GHz WEP network is visible in scan result.
+        2. Connect to the network and validate internet connection.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wep_networks[0]["5g"])
+
+    @test_tracker_info(uuid="4a957952-289d-4657-9882-e1475274a7ff")
+    def test_connect_to_wpa_2g(self):
+        """Verify DUT can connect to 2GHz WPA-PSK network
+
+        Steps:
+        1. Ensure the 2GHz WPA-PSK network is visible in scan result.
+        2. Connect to the network and validate internet connection.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["2g"])
+
+    @test_tracker_info(uuid="612c3c31-a4c5-4014-9a2d-3f4bcc20c0d7")
+    def test_connect_to_wpa_5g(self):
+        """Verify DUT can connect to 5GHz WPA-PSK network
+
+        Steps:
+        1. Ensure the 5GHz WPA-PSK network is visible in scan result.
+        2. Connect to the network and validate internet connection.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["5g"])
+
+    @test_tracker_info(uuid="2a617fb4-1d8e-44e9-a500-a5456e1df83f")
+    def test_connect_to_2g_can_be_pinged(self):
+        """Verify DUT can be pinged by another device when it connects to 2GHz AP
+
+        Steps:
+        1. Ensure the 2GHz WPA-PSK network is visible in scan result.
+        2. Connect to the network and validate internet connection.
+        3. Check DUT can be pinged by another device
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["2g"])
+        wutils.connect_to_wifi_network(self.dut_client, self.wpa_networks[0]["2g"])
+        self.verify_traffic_between_devices(self.dut,self.dut_client)
+
+    @test_tracker_info(uuid="94bdd657-649b-4a2c-89c3-3ec6ba18e08e")
+    def test_connect_to_5g_can_be_pinged(self):
+        """Verify DUT can be pinged by another device when it connects to 5GHz AP
+
+        Steps:
+        1. Ensure the 5GHz WPA-PSK network is visible in scan result.
+        2. Connect to the network and validate internet connection.
+        3. Check DUT can be pinged by another device
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["5g"])
+        wutils.connect_to_wifi_network(self.dut_client, self.wpa_networks[0]["5g"])
+        self.verify_traffic_between_devices(self.dut,self.dut_client)
+
+    @test_tracker_info(uuid="d87359aa-c4da-4554-b5de-8e3fa852a6b0")
+    def test_sta_turn_off_screen_can_be_pinged(self):
+        """Verify DUT can be pinged by another device after idle for a while
+
+        Steps:
+        1. Ensure the 2GHz WPA-PSK network is visible in scan result.
+        2. DUT and DUT_Client connect to the network and validate internet connection.
+        3. Let DUT sleep for 5 minutes
+        4. Check DUT can be pinged by DUT_Client
+        """
+        asserts.skip_if(len(self.android_devices) < 3, "Need 3 devices")
+        self.dut_client_a = self.android_devices[1]
+        self.dut_client_b = self.android_devices[2]
+
+        # enable hotspot on dut and connect client devices to it
+        ap_ssid = "softap_" + acts.utils.rand_ascii_str(8)
+        ap_password = acts.utils.rand_ascii_str(8)
+        self.dut.log.info("softap setup: %s %s", ap_ssid, ap_password)
+        config = {wutils.WifiEnums.SSID_KEY: ap_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = ap_password
+        wutils.start_wifi_tethering(
+            self.dut,
+            config[wutils.WifiEnums.SSID_KEY],
+            config[wutils.WifiEnums.PWD_KEY],
+            wutils.WifiEnums.WIFI_CONFIG_APBAND_AUTO)
+
+        # DUT connect to AP
+        wutils.connect_to_wifi_network(
+            self.dut_client_a, config, check_connectivity=False)
+        wutils.connect_to_wifi_network(
+            self.dut_client_b, config, check_connectivity=False)
+        # Check DUT and DUT_Client can ping each other successfully
+        self.verify_traffic_between_devices(self.dut_client_a,
+                                            self.dut_client_b)
+        self.verify_traffic_between_devices(self.dut_client_a,
+                                            self.dut_client_b)
+
+        # DUT turn off screen and go sleep for 5 mins
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        # TODO(hsiuchangchen): find a way to check system already suspended
+        #                      instead of waiting 5 mins
+        self.log.info("Sleep for 5 minutes")
+        time.sleep(300)
+        # Verify DUT_Client can ping DUT when DUT sleeps
+        self.verify_traffic_between_devices(self.dut_client_a,
+                                            self.dut_client_b)
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    @test_tracker_info(uuid="402cfaa8-297f-4865-9e27-6bab6adca756")
+    def test_reboot_wifi_and_bluetooth_on(self):
+        """Toggle WiFi and bluetooth ON then reboot """
+        wutils.wifi_toggle_state(self.dut, True)
+        enable_bluetooth(self.dut.droid, self.dut.ed)
+
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+
+        asserts.assert_true(self.dut.droid.bluetoothCheckState(),
+                "bluetooth state changed after reboot")
+        asserts.assert_true(self.dut.droid.wifiCheckState(),
+                "wifi state changed after reboot")
+
+        disable_bluetooth(self.dut.droid)
+
+    @test_tracker_info(uuid="d0e14a2d-a28f-4551-8988-1e15d9d8bb1a")
+    def test_scan_result_api(self):
+        """Register scan result callback, start scan and wait for event"""
+        self.dut.ed.clear_all_events()
+        self.dut.droid.wifiStartScanWithListener()
+        try:
+            events = self.dut.ed.pop_events(
+                "WifiManagerScanResultsCallbackOnSuccess", 60)
+        except queue.Empty:
+            asserts.fail(
+                "Wi-Fi scan results did not become available within 60s.")
+
+    @test_tracker_info(uuid="03cfbc86-7fcc-48d8-ab0f-1f6f3523e596")
+    def test_enable_disable_auto_join_saved_network(self):
+        """
+        Add a saved network, simulate user change the auto join to false, ensure the device doesn't
+        auto connect to this network
+
+        Steps:
+        1. Create a saved network.
+        2. Add this saved network, and ensure we connect to this network
+        3. Simulate user change the auto join to false.
+        4. Toggle the Wifi off and on
+        4. Ensure device doesn't connect to his network
+        """
+        network = self.open_network_5g
+        wutils.connect_to_wifi_network(self.dut, network)
+        info = self.dut.droid.wifiGetConnectionInfo()
+        network_id = info[WifiEnums.NETID_KEY]
+        self.dut.log.info("Disable auto join on network")
+        self.dut.droid.wifiEnableAutojoin(network_id, False)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, network[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False), "Device should not connect.")
+        self.dut.droid.wifiEnableAutojoin(network_id, True)
+        wutils.wait_for_connect(self.dut, network[WifiEnums.SSID_KEY], assert_on_fail=False)
diff --git a/acts_tests/tests/google/wifi/WifiNativeTest.py b/acts_tests/tests/google/wifi/WifiNativeTest.py
new file mode 100644
index 0000000..56e2195
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiNativeTest.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from acts import base_test
+from acts.controllers import native_android_device
+
+class WifiNativeTest(base_test.BaseTestClass):
+
+    def setup_class(self):
+        self.native_devices = self.register_controller(native_android_device)
+        self.dut = self.native_devices[0]
+
+    def setup_test(self):
+#        TODO: uncomment once wifi_toggle_state (or alternative)
+#              work with sl4n
+#        assert wutils.wifi_toggle_state(self.device, True)
+        return self.dut.droid.WifiInit()
+
+#   TODO: uncomment once wifi_toggle_state (or alternative)
+#         work with sl4n
+#    def teardown_test(self):
+#        assert wutils.wifi_toggle_state(self.device, False)
+
+    def test_hal_get_features(self):
+        result = self.dut.droid.WifiGetSupportedFeatureSet()
+        self.log.info("Wi-Fi feature set: %d", result)
diff --git a/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py b/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
new file mode 100644
index 0000000..81aa86d
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
@@ -0,0 +1,494 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+from acts import asserts
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.wifi import wifi_constants
+
+WifiEnums = wutils.WifiEnums
+
+# Network request timeout to use.
+NETWORK_REQUEST_TIMEOUT_MS = 60 * 1000
+# Timeout to wait for instant failure.
+NETWORK_REQUEST_INSTANT_FAILURE_TIMEOUT_SEC = 5
+
+class WifiNetworkRequestTest(WifiBaseTest):
+    """Tests for NetworkRequest with WifiNetworkSpecifier API surface.
+
+    Test Bed Requirement:
+    * one Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = []
+        opt_param = [
+            "open_network", "reference_networks"
+        ]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(wpa_network=True,
+                                               wep_network=True)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                wep_network=True)
+
+        asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+        self.wpa_psk_2g = self.reference_networks[0]["2g"]
+        self.wpa_psk_5g = self.reference_networks[0]["5g"]
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        self.remove_approvals()
+        self.clear_user_disabled_networks()
+        wutils.wifi_toggle_state(self.dut, True)
+        self.dut.ed.clear_all_events()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        self.dut.droid.wifiReleaseNetworkAll()
+        self.dut.droid.wifiDisconnect()
+        wutils.reset_wifi(self.dut)
+        # Ensure we disconnected from the current network before the next test.
+        if self.dut.droid.wifiGetConnectionInfo()["supplicant_state"] != "disconnected":
+            wutils.wait_for_disconnect(self.dut)
+        wutils.wifi_toggle_state(self.dut, False)
+        self.dut.ed.clear_all_events()
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+    def wait_for_network_lost(self):
+        """
+        Wait for network lost callback from connectivity service (wifi
+        disconnect).
+
+        Args:
+            ad: Android device object.
+        """
+        try:
+            self.dut.droid.wifiStartTrackingStateChange()
+            event = self.dut.ed.pop_event(
+                wifi_constants.WIFI_NETWORK_CB_ON_LOST, 10)
+            self.dut.droid.wifiStopTrackingStateChange()
+        except queue.Empty:
+            raise signals.TestFailure(
+                "Device did not disconnect from the network")
+
+    def remove_approvals(self):
+        self.dut.log.debug("Removing all approvals from sl4a app")
+        self.dut.adb.shell(
+            "cmd wifi network-requests-remove-user-approved-access-points"
+            + " " + SL4A_APK_NAME)
+
+    def clear_user_disabled_networks(self):
+        self.dut.log.debug("Clearing user disabled networks")
+        self.dut.adb.shell(
+            "cmd wifi clear-user-disabled-networks")
+
+    @test_tracker_info(uuid="d70c8380-61ba-48a3-b76c-a0b55ce4eabf")
+    def test_connect_to_wpa_psk_2g_with_ssid(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+
+        Steps:
+        1. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 2G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        """
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
+                                                  self.wpa_psk_2g)
+
+    @test_tracker_info(uuid="44f2bf40-a282-4413-b8f2-3abb3caa49ee")
+    def test_connect_to_open_5g_with_ssid(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+
+        Steps:
+        1. Send a network specifier with the specific SSID of Open 5G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        """
+        wutils.wifi_connect_using_network_request(self.dut, self.open_5g,
+                                                  self.open_5g)
+
+    @test_tracker_info(uuid="09d1823e-4f85-42f8-8c20-de7637f6d4be")
+    def test_connect_to_wpa_psk_5g_with_ssid_pattern(self):
+        """
+        Initiates a connection to network via network request with SSID pattern
+
+        Steps:
+        1. Send a network specifier with the SSID pattern/credentials of
+           WPA-PSK 5G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        """
+        network_specifier = self.wpa_psk_5g.copy();
+        # Remove ssid & replace with ssid pattern.
+        network_ssid = network_specifier.pop(WifiEnums.SSID_KEY)
+        # Remove the last element of ssid & replace with .* to create a matching
+        # pattern.
+        network_specifier[WifiEnums.SSID_PATTERN_KEY] = network_ssid[:-1] + ".*"
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_5g,
+                                                  network_specifier)
+
+    @test_tracker_info(uuid="52216329-06f1-45ef-8d5f-de8b02d9f975")
+    def test_connect_to_open_5g_after_connecting_to_wpa_psk_2g(self):
+        """
+        Initiates a connection to network via network request with SSID pattern
+
+        Steps:
+        1. Send a network specifier with the specific SSID of Open 5G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        5. Release the network request.
+        6. Send another network specifier with the specific SSID & credentials
+           of WPA-PSK 2G network.
+        7. Ensure we disconnect from the previous network.
+        8. Wait for platform to scan and find matching networks.
+        9. Simulate user selecting the new network.
+        10. Ensure that the device connects to the new network.
+        """
+        # Complete flow for the first request.
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
+                                                  self.wpa_psk_2g)
+        # Release the request.
+        self.dut.droid.wifiReleaseNetwork(self.wpa_psk_2g)
+        # Ensure we disconnected from the previous network.
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
+        self.dut.ed.clear_all_events()
+        # Complete flow for the second request.
+        wutils.wifi_connect_using_network_request(self.dut, self.open_5g,
+                                                  self.open_5g)
+
+    @test_tracker_info(uuid="f28b5dc9-771f-42ef-8178-e55e9a16f5c7")
+    def test_connect_to_wpa_psk_5g_while_connecting_to_open_2g(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+
+        Steps:
+        1. Send a network specifier with the specific SSID & credentials of
+           WPA-PSK 5G network.
+        2. Send another network specifier with the specific SSID of Open 2G
+           network.
+        3. Ensure we disconnect from the previous network.
+        4. Wait for platform to scan and find matching networks.
+        5. Simulate user selecting the new network.
+        6. Ensure that the device connects to the new network.
+        """
+        # Make the first request.
+        self.dut.droid.wifiRequestNetworkWithSpecifier(self.open_2g)
+        self.dut.log.info("Sent network request with %s", self.open_2g)
+        # Complete flow for the second request.
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_5g,
+                                                  self.wpa_psk_5g)
+
+    @test_tracker_info(uuid="2ab82a98-37da-4b27-abb6-578bedebccdc")
+    def test_connect_to_open_5g_while_connected_to_wpa_psk_2g(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+
+        Steps:
+        1. Send a network specifier with the specific SSID of Open 5G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        5. Send another network specifier with the specific SSID & credentials
+           of WPA-PSK 2G network.
+        6. Ensure we disconnect from the previous network.
+        7. Wait for platform to scan and find matching networks.
+        8. Simulate user selecting the new network.
+        9. Ensure that the device connects to the new network.
+        """
+        # Complete flow for the first request.
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
+                                                  self.wpa_psk_2g)
+        # Send the second request.
+        self.dut.droid.wifiRequestNetworkWithSpecifier(self.open_5g)
+        self.dut.log.info("Sent network request with %s", self.open_5g)
+        # Ensure we do not disconnect from the previous network until the user
+        # approves the new request.
+        self.dut.ed.clear_all_events()
+        wutils.ensure_no_disconnect(self.dut)
+
+        # Now complete the flow and ensure we connected to second request.
+        wutils.wait_for_wifi_connect_after_network_request(self.dut,
+                                                           self.open_5g)
+
+    @test_tracker_info(uuid="f0bb2213-b3d1-4fb8-bbdc-ad55c4fb05ed")
+    def test_connect_to_wpa_psk_2g_which_is_already_approved(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+        bypassing user approval.
+
+        Steps:
+        1. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 2G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        5. Ensure we disconnect from the previous network.
+        6. Send another network specifier with the specific
+           SSID/BSSID/credentials of WPA-PSK 2G network.
+        7. Ensure that the device bypasses user approval & connects to the
+           same network.
+        """
+        # Complete flow for the first request.
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
+                                                  self.wpa_psk_2g)
+        # Release the request.
+        self.dut.droid.wifiReleaseNetwork(self.wpa_psk_2g)
+        # Ensure we disconnected from the network.
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
+        self.dut.ed.clear_all_events()
+
+        # Find bssid for the WPA-PSK 2G network.
+        scan_results = self.dut.droid.wifiGetScanResults()
+        match_results = wutils.match_networks(
+            {WifiEnums.SSID_KEY: self.wpa_psk_2g[WifiEnums.SSID_KEY]},
+            scan_results)
+        asserts.assert_equal(len(match_results), 1,
+                             "Cannot find bssid for WPA-PSK 2G network")
+        bssid = match_results[0][WifiEnums.BSSID_KEY]
+        # Send the second request with bssid.
+        network_specifier_with_bssid = self.wpa_psk_2g.copy();
+        network_specifier_with_bssid[WifiEnums.BSSID_KEY] = bssid
+        self.dut.droid.wifiRequestNetworkWithSpecifier(
+            network_specifier_with_bssid)
+        self.dut.log.info("Sent network request with %r",
+                          network_specifier_with_bssid)
+
+        # Ensure we connected to second request without user approval.
+        wutils.wait_for_connect(self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="fcf84d94-5f6e-4bd6-9f76-40a0228d4ebe")
+    def test_connect_to_wpa_psk_2g_which_is_already_approved_but_then_forgot(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+        with user approval.
+
+        Steps:
+        1. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 2G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user selecting the network.
+        4. Ensure that the device connects to the network.
+        4. Simulate user fogetting the network from the UI.
+        6. Ensure we disconnect from the previous network.
+        7. Send another network specifier with the specific
+           SSID/BSSID/credentials of WPA-PSK 2G network.
+        8. Ensure that the device does not bypass user approval & connects to the
+           same network with user approval. (This should also clear the blacklist)
+        9. Send the same network specifier with the specific
+           SSID/BSSID/credentials of WPA-PSK 2G network.
+        10.Ensure that the device bypasses user approval now & connects to the
+           same network.
+        """
+        # Complete flow for the first request.
+        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
+                                                  self.wpa_psk_2g)
+
+        # Simulate user forgeting the ephemeral network.
+        self.dut.droid.wifiUserDisconnectNetwork(self.wpa_psk_2g[WifiEnums.SSID_KEY])
+        # Ensure we disconnected from the network.
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
+        self.dut.ed.clear_all_events()
+        # Release the first request.
+        self.dut.droid.wifiReleaseNetwork(self.wpa_psk_2g)
+
+        # Find bssid for the WPA-PSK 2G network.
+        scan_results = self.dut.droid.wifiGetScanResults()
+        match_results = wutils.match_networks(
+            {WifiEnums.SSID_KEY: self.wpa_psk_2g[WifiEnums.SSID_KEY]},
+            scan_results)
+        asserts.assert_equal(len(match_results), 1,
+                             "Cannot find bssid for WPA-PSK 2G network")
+        bssid = match_results[0][WifiEnums.BSSID_KEY]
+        # Send the second request with bssid.
+        network_specifier_with_bssid = self.wpa_psk_2g.copy();
+        network_specifier_with_bssid[WifiEnums.BSSID_KEY] = bssid
+        self.dut.droid.wifiRequestNetworkWithSpecifier(
+            network_specifier_with_bssid)
+        self.dut.log.info("Sent network request with %r",
+                          network_specifier_with_bssid)
+
+        # Ensure that we did not connect bypassing user approval.
+        assert_msg = "Device should not connect without user approval"
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut,
+                                    self.wpa_psk_2g[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False),
+            assert_msg)
+
+        # Now complete the flow and ensure we connected to second request.
+        wutils.wait_for_wifi_connect_after_network_request(self.dut,
+                                                           self.wpa_psk_2g)
+
+        # Now make the same request again & ensure that we connect without user
+        # approval.
+        self.dut.droid.wifiRequestNetworkWithSpecifier(
+            network_specifier_with_bssid)
+        self.dut.log.info("Sent network request with %r",
+                          network_specifier_with_bssid)
+        wutils.wait_for_connect(self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="2f90a266-f04d-4932-bb5b-d075bedfd56d")
+    def test_match_failure_with_invalid_ssid_pattern(self):
+        """
+        Initiates a connection to network via network request with SSID pattern
+        that does not match any networks.
+
+        Steps:
+        1. Trigger a connect to one of the networks (as a saved network).
+        2. Send a network specifier with the non-matching SSID pattern.
+        3. Ensure that the platform does not return any matching networks.
+        4. Wait for the request to timeout.
+        """
+        network = self.wpa_psk_5g
+
+        # Trigger a connection to a network as a saved network before the
+        # request and ensure that this does not change the behavior.
+        wutils.connect_to_wifi_network(self.dut, network, check_connectivity=False)
+
+        network_specifier = self.wpa_psk_5g.copy();
+        # Remove ssid & replace with invalid ssid pattern.
+        network_ssid = network_specifier.pop(WifiEnums.SSID_KEY)
+        network_specifier[WifiEnums.SSID_PATTERN_KEY] = \
+            network_ssid + "blah" + ".*"
+
+        self.dut.droid.wifiStartTrackingStateChange()
+        expected_ssid = network[WifiEnums.SSID_KEY]
+
+        self.dut.droid.wifiRequestNetworkWithSpecifierWithTimeout(
+              network_specifier, NETWORK_REQUEST_TIMEOUT_MS)
+        self.dut.log.info("Sent network request with invalid specifier %s",
+                    network_specifier)
+        time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
+        self.dut.droid.wifiRegisterNetworkRequestMatchCallback()
+        # Wait for the request to timeout.
+        timeout_secs = NETWORK_REQUEST_TIMEOUT_MS * 2 / 1000
+        try:
+            on_unavailable_event = self.dut.ed.pop_event(
+                wifi_constants.WIFI_NETWORK_CB_ON_UNAVAILABLE, timeout_secs)
+            asserts.assert_true(on_unavailable_event, "Network request did not timeout")
+        except queue.Empty:
+            asserts.fail("No events returned")
+        finally:
+            self.dut.droid.wifiStopTrackingStateChange()
+
+    @test_tracker_info(uuid="caa96f57-840e-4997-9280-655edd3b76ee")
+    def test_connect_failure_user_rejected(self):
+        """
+        Initiates a connection to network via network request with specific SSID
+        which the user rejected.
+
+        Steps:
+        1. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 5G network.
+        2. Wait for platform to scan and find matching networks.
+        3. Simulate user rejecting the network.
+        4. Ensure that we get an instant onUnavailable callback.
+        5. Simulate user fogetting the network from the UI.
+        """
+        network = self.wpa_psk_5g
+        expected_ssid = network[WifiEnums.SSID_KEY]
+
+        self.dut.droid.wifiStartTrackingStateChange()
+
+        self.dut.droid.wifiRequestNetworkWithSpecifierWithTimeout(
+              network, NETWORK_REQUEST_TIMEOUT_MS)
+        self.dut.log.info("Sent network request with specifier %s", network)
+        time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
+        self.dut.droid.wifiRegisterNetworkRequestMatchCallback()
+
+        # Wait for the platform to scan and return a list of networks
+        # matching the request
+        try:
+            matched_network = None
+            for _ in [0,  3]:
+                on_match_event = self.dut.ed.pop_event(
+                    wifi_constants.WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH, 30)
+                asserts.assert_true(on_match_event,
+                                    "Network request on match not received.")
+                matched_scan_results = on_match_event["data"]
+                self.dut.log.debug("Network request on match results %s",
+                                   matched_scan_results)
+                matched_network = wutils.match_networks(
+                    {WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]},
+                     matched_scan_results)
+                if matched_network:
+                    break;
+            asserts.assert_true(
+                matched_network, "Target network %s not found" % network)
+
+            # Send user rejection.
+            self.dut.droid.wifiSendUserRejectionForNetworkRequestMatch()
+            self.dut.log.info("Sent user rejection for network request %s",
+                              expected_ssid)
+
+            # Wait for the platform to raise unavailable callback
+            # instantaneously.
+            on_unavailable_event = self.dut.ed.pop_event(
+                wifi_constants.WIFI_NETWORK_CB_ON_UNAVAILABLE,
+                NETWORK_REQUEST_INSTANT_FAILURE_TIMEOUT_SEC)
+            asserts.assert_true(on_unavailable_event,
+                                "Network request on available not received.")
+        except queue.Empty:
+            asserts.fail("Expected events not returned")
+        finally:
+            self.dut.droid.wifiStopTrackingStateChange()
diff --git a/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py b/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
new file mode 100644
index 0000000..7de603c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
@@ -0,0 +1,448 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import time
+
+import acts.signals as signals
+
+from acts import asserts
+from acts import base_test
+from acts.controllers import android_device
+from acts.controllers import attenuator
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+AP_1 = 0
+AP_2 = 1
+# WifiNetworkSelector imposes a 10 seconds gap between two selections
+NETWORK_SELECTION_TIME_GAP = 12
+LVL1_ATTN = 15
+LVL2_ATTN = 30
+MIN_ATTN = 0
+MAX_ATTN = 95
+ATTN_SLEEP = 12
+
+
+class WifiNetworkSelectorTest(WifiBaseTest):
+    """These tests verify the behavior of the Android Wi-Fi Network Selector
+    feature.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        self.ap1_2g_attn = 0
+        self.ap1_5g_attn = 1
+        self.ap2_2g_attn = 2
+        self.ap2_5g_attn = 3
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(mirror_ap=False,
+                                               ap_count=2)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                ap_count=2)
+            self.ap1_5g_attn, self.ap2_2g_attn, self.ap2_5g_attn, = 0, 1, 1
+        self.configure_packet_capture()
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        self.dut.ed.clear_all_events()
+        for a in self.attenuators:
+            a.set_atten(MAX_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+    def teardown_test(self):
+        super().teardown_test()
+        for a in self.attenuators:
+            a.set_atten(MIN_ATTN)
+        wutils.reset_wifi(self.dut)
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """ Helper Functions """
+
+    def add_networks(self, ad, networks):
+        """Add Wi-Fi networks to an Android device and verify the networks were
+        added correctly.
+
+        Args:
+            ad: the AndroidDevice object to add networks to.
+            networks: a list of dicts, each dict represents a Wi-Fi network.
+        """
+        for network in networks:
+            ret = ad.droid.wifiAddNetwork(network)
+            asserts.assert_true(ret != -1,
+                                "Failed to add network %s" % network)
+            ad.droid.wifiEnableNetwork(ret, 0)
+
+        configured_networks = ad.droid.wifiGetConfiguredNetworks()
+        self.log.info("Configured networks: %s", configured_networks)
+
+    def connect_and_verify_connected_bssid(self, network):
+        """Start a scan to get the DUT connected to an AP and verify the DUT
+        is connected to the correct BSSID.
+
+        Args:
+            expected_bssid: Network bssid to which connection.
+
+        Returns:
+            True if connection to given network happen, else return False.
+        """
+        expected_ssid = network['SSID']
+        expected_bssid = network['bssid']
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, expected_ssid)
+        time.sleep(20)
+        actual_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Actual network: %s", actual_network)
+        asserts.assert_true(
+            actual_network and WifiEnums.BSSID_KEY in actual_network and \
+                expected_bssid.lower() == actual_network[
+                    WifiEnums.BSSID_KEY].lower(),
+            "Expected BSSID: %s, Actual BSSID: %s" %
+            (expected_bssid, actual_network[WifiEnums.BSSID_KEY]))
+        self.log.info("DUT connected to valid network: %s" % expected_bssid)
+
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="ffa5e278-db3f-4e17-af11-6c7a3e7c5cc2")
+    def test_network_selector_automatic_connection(self):
+        """
+            1. Add one saved network to DUT.
+            2. Move the DUT in range.
+            3. Verify the DUT is connected to the network.
+        """
+        # add a saved network to DUT
+        networks = [self.reference_networks[AP_1]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # move the DUT in range
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected to AP_1 5g network
+        network = self.reference_networks[AP_1]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="3ea818f2-10d7-4aad-bfab-7d8fb25aae78")
+    def test_network_selector_basic_connection_prefer_5g(self):
+        """
+            1. Add one saved SSID with 2G and 5G BSSIDs of similar RSSI.
+            2. Move the DUT in range.
+            3. Verify the DUT is connected to the 5G BSSID.
+        """
+        # add a saved network with both 2G and 5G BSSIDs to DUT
+        networks = [self.reference_networks[AP_1]['2g'],
+                    self.reference_networks[AP_1]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # Move DUT in range
+        self.attenuators[self.ap1_2g_attn].set_atten(MIN_ATTN)
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected to 5G network
+        network = self.reference_networks[AP_1]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="bebb29ca-4486-4cde-b390-c5f8f2e1580c")
+    def test_network_selector_prefer_stronger_rssi(self):
+        """
+            1. Add two saved SSIDs to DUT, same band, one has stronger RSSI
+               than the other.
+            2. Move the DUT in range.
+            3. Verify the DUT is connected to the SSID with stronger RSSI.
+        """
+        # add a 2G and a 5G saved network to DUT
+        networks = [self.reference_networks[AP_1]['2g'],
+                    self.reference_networks[AP_2]['2g']]
+        self.add_networks(self.dut, networks)
+
+        # move the DUT in range
+        self.attenuators[self.ap1_2g_attn].set_atten(LVL1_ATTN)
+        self.attenuators[self.ap2_2g_attn].set_atten(LVL2_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected AP_1
+        network = self.reference_networks[AP_1]['2g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['2g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="f9f72dc5-034f-4fe2-a27d-df1b6cae76cd")
+    def test_network_selector_prefer_secure_over_open_network(self):
+        """
+            1. Add two saved networks to DUT, same band, similar RSSI, one uses
+               WPA2 security, the other is open.
+            2. Move the DUT in range.
+            3. Verify the DUT is connected to the secure network that uses WPA2.
+        """
+        # add a open network and a secure saved network to DUT
+        networks = [self.open_network[AP_1]['5g'],
+                    self.reference_networks[AP_1]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # Move DUT in range
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT connects to secure network
+        network = self.reference_networks[AP_1]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="ab2c527c-0f9c-4f09-a13f-e3f461b7da52")
+    def test_network_selector_blacklist_by_connection_failure(self):
+        """
+            1. Add two saved secured networks X and Y to DUT. X has stronger
+               RSSI than Y. X has wrong password configured.
+            2. Move the DUT in range.
+            3. Verify the DUT is connected to network Y.
+        """
+        # add two saved networks to DUT, and one of them is configured with
+        # incorrect password
+        wrong_passwd_network = self.reference_networks[AP_1]['5g'].copy()
+        wrong_passwd_network['password'] += 'haha'
+        networks = [wrong_passwd_network, self.reference_networks[AP_2]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # make AP_1 5G has stronger RSSI than AP_2 5G
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
+        self.attenuators[self.ap2_5g_attn].set_atten(LVL1_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # start 3 scans to get AP_1 5G blacklisted because of the incorrect
+        # password
+        for _ in range(3):
+            wutils.start_wifi_connection_scan_and_return_status(self.dut)
+            time.sleep(NETWORK_SELECTION_TIME_GAP)
+
+        # verify DUT is connect AP_2 5G
+        network = self.reference_networks[AP_2]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_2]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="71d88fcf-c7b8-4fd2-a7cb-84ac4a130ecf")
+    def network_selector_2g_to_5g_prefer_same_SSID(self):
+        """
+            1. Add SSID_A and SSID_B to DUT. Both SSIDs have both 2G and 5G
+               BSSIDs.
+            2. Attenuate the networks so that the DUT is connected to SSID_A's
+               2G in the beginning.
+            3. Increase the RSSI of both SSID_A's 5G and SSID_B's 5G.
+            4. Verify the DUT switches to SSID_A's 5G.
+        """
+        #add two saved networks to DUT
+        networks = [
+            self.reference_networks[AP_1]['2g'], self.reference_networks[AP_2][
+                '2g']
+        ]
+        self.add_networks(self.dut, networks)
+        #make AP_1 2G in range
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(0)
+        #verify
+        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
+            '2g']['bssid'])
+        #make both AP_1 and AP_2 5G in range with similar RSSI
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(0)
+        #ensure the time gap between two network selections
+        time.sleep(NETWORK_SELECTION_TIME_GAP)
+        #verify
+        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
+            '5g']['bssid'])
+
+    @test_tracker_info(uuid="c1243cf4-d96e-427e-869e-3d640bee3f28")
+    def network_selector_2g_to_5g_different_ssid(self):
+        """
+            1. Add SSID_A and SSID_B to DUT. Both SSIDs have both 2G and 5G
+               BSSIDs.
+            2. Attenuate the networks so that the DUT is connected to SSID_A's
+               2G in the beginning.
+            3. Increase the RSSI of SSID_B's 5G while attenuate down SSID_A's
+               2G RSSI.
+            4. Verify the DUT switches to SSID_B's 5G.
+        """
+        # add two saved networks to DUT
+        networks = [self.reference_networks[AP_1]['2g'],
+                    self.reference_networks[AP_2]['2g']]
+        self.add_networks(self.dut, networks)
+
+        # make both AP_1 2G and AP_2 5G in range, and AP_1 2G
+        # has much stronger RSSI than AP_2 5G
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(0)
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(20)
+        #verify
+        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
+            '2g']['bssid'])
+        #bump up AP_2 5G RSSI and reduce AP_1 2G RSSI
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(40)
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(0)
+        #ensure the time gap between two network selections
+        time.sleep(NETWORK_SELECTION_TIME_GAP)
+        #verify
+        self.connect_and_verify_connected_bssid(self.reference_networks[AP_2][
+            '5g']['bssid'])
+
+    @test_tracker_info(uuid="10da95df-83ed-4447-89f8-735b08dbe2eb")
+    def network_selector_5g_to_2g_same_ssid(self):
+        """
+            1. Add one SSID that has both 2G and 5G to the DUT.
+            2. Attenuate down the 2G RSSI.
+            3. Connect the DUT to the 5G BSSID.
+            4. Bring up the 2G RSSI and attenuate down the 5G RSSI.
+            5. Verify the DUT switches to the 2G BSSID.
+        """
+        #add a saved network to DUT
+        networks = [self.reference_networks[AP_1]['2g']]
+        self.add_networks(self.dut, networks)
+        #make both AP_1 2G and AP_2 5G in range, and AP_1 5G
+        #has much stronger RSSI than AP_2 2G
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(50)
+        #verify
+        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
+            '5g']['bssid'])
+        #bump up AP_1 2G RSSI and reduce AP_1 5G RSSI
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(0)
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(30)
+        #ensure the time gap between two network selections
+        time.sleep(NETWORK_SELECTION_TIME_GAP)
+        #verify
+        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
+            '2g']['bssid'])
+
+    @test_tracker_info(uuid="ead78ae0-27ab-4bb8-ae77-0b9fe588436a")
+    def test_network_selector_stay_on_sufficient_network(self):
+        """
+            1. Add two 5G WPA2 BSSIDs X and Y to the DUT. X has higher RSSI
+               than Y.
+            2. Connect the DUT to X.
+            3. Change attenuation so that Y's RSSI goes above X's.
+            4. Verify the DUT stays on X.
+        """
+        # add two saved networks to DUT
+        networks = [self.reference_networks[AP_1]['5g'],
+                    self.reference_networks[AP_2]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # make both AP_1 5G and AP_2 5G in range, and AP_1 5G
+        # has stronger RSSI than AP_2 5G
+        self.attenuators[self.ap1_5g_attn].set_atten(LVL1_ATTN)
+        self.attenuators[self.ap2_5g_attn].set_atten(LVL2_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected to AP_1
+        network = self.reference_networks[AP_1]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+        # bump up AP_2 5G RSSI over AP_1 5G RSSI
+        self.attenuators[self.ap2_5g_attn].set_atten(MIN_ATTN)
+
+        # ensure the time gap between two network selections
+        time.sleep(NETWORK_SELECTION_TIME_GAP)
+
+        # verify DUT is still connected to AP_1
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="5470010f-8b62-4b1c-8b83-1f91422eced0")
+    def test_network_selector_stay_on_user_selected_network(self):
+        """
+            1. Connect the DUT to SSID_A with a very low RSSI via the user select code path.
+            2. Add SSID_B to the DUT as saved network. SSID_B has higher RSSI than SSID_A.
+            3. Start a scan and network selection.
+            4. Verify DUT stays on SSID_A.
+        """
+        # set max attenuation on AP_2 and make AP_1 5G in range with low RSSI
+        self.attenuators[self.ap2_5g_attn].set_atten(MIN_ATTN)
+        self.attenuators[self.ap1_5g_attn].set_atten(LVL1_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # connect to AP_1 via user selection and add, save AP_2
+        wutils.connect_to_wifi_network(
+            self.dut, self.reference_networks[AP_1]['5g'])
+        networks = [self.reference_networks[AP_2]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # ensure the time gap between two network selections
+        time.sleep(NETWORK_SELECTION_TIME_GAP)
+
+        # verify we are still connected to AP_1 5G
+        network = self.reference_networks[AP_1]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+    @test_tracker_info(uuid="f08d8f73-8c94-42af-bba9-4c49bbf16420")
+    def test_network_selector_reselect_after_forget_network(self):
+        """
+            1. Add two 5G BSSIDs X and Y to the DUT. X has higher RSSI
+               than Y.
+            2. Connect the DUT to X.
+            3. Forget X.
+            5. Verify the DUT reselect and connect to Y.
+        """
+        # add two networks to DUT
+        networks = [self.reference_networks[AP_1]['5g'],
+                    self.reference_networks[AP_2]['5g']]
+        self.add_networks(self.dut, networks)
+
+        # make both AP_1 5G and AP_2 5G in range. AP_1 5G has stronger
+        # RSSI than AP_2 5G
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
+        self.attenuators[self.ap2_5g_attn].set_atten(LVL1_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT connected to AP1
+        network = self.reference_networks[AP_1]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_1]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
+
+        # forget AP_1
+        wutils.wifi_forget_network(
+            self.dut, self.reference_networks[AP_1]['5g']['SSID'])
+
+        # verify DUT connected to AP2
+        network = self.reference_networks[AP_2]['5g'].copy()
+        if "OpenWrtAP" in self.user_params:
+            network['bssid'] = self.bssid_map[AP_2]['5g'][network["SSID"]]
+        self.connect_and_verify_connected_bssid(network)
diff --git a/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py b/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
new file mode 100644
index 0000000..bdca451
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
@@ -0,0 +1,880 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+from acts import asserts
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.wifi import wifi_constants
+
+WifiEnums = wutils.WifiEnums
+# EAP Macros
+EAP = WifiEnums.Eap
+EapPhase2 = WifiEnums.EapPhase2
+# Enterprise Config Macros
+Ent = WifiEnums.Enterprise
+ATT = 2
+# Suggestion network Macros
+Untrusted = "untrusted"
+AutoJoin = "enableAutojoin"
+# Network request Macros
+ClearCapabilities = "ClearCapabilities"
+TransportType = "TransportType"
+
+
+# Default timeout used for reboot, toggle WiFi and Airplane mode,
+# for the system to settle down after the operation.
+DEFAULT_TIMEOUT = 10
+PASSPOINT_TIMEOUT = 30
+
+
+class WifiNetworkSuggestionTest(WifiBaseTest):
+    """Tests for WifiNetworkSuggestion API surface.
+
+    Test Bed Requirement:
+    * one Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        opt_param = [
+            "open_network", "reference_networks", "hidden_networks", "radius_conf_2g",
+            "radius_conf_5g", "ca_cert", "eap_identity", "eap_password", "passpoint_networks",
+            "altsubject_match"]
+        self.unpack_userparams(opt_param_names=opt_param,)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(
+                wpa_network=True, ent_network=True,
+                radius_conf_2g=self.radius_conf_2g,
+                radius_conf_5g=self.radius_conf_5g,)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,)
+        if hasattr(self, "reference_networks") and \
+            isinstance(self.reference_networks, list):
+              self.wpa_psk_2g = self.reference_networks[0]["2g"]
+              self.wpa_psk_5g = self.reference_networks[0]["5g"]
+        if hasattr(self, "open_network") and isinstance(self.open_network,list):
+            self.open_2g = self.open_network[0]["2g"]
+            self.open_5g = self.open_network[0]["5g"]
+        if hasattr(self, "hidden_networks") and \
+            isinstance(self.hidden_networks, list):
+              self.hidden_network = self.hidden_networks[0]
+        if hasattr(self, "passpoint_networks"):
+            self.passpoint_network = self.passpoint_networks[ATT]
+            self.passpoint_network[WifiEnums.SSID_KEY] = \
+                self.passpoint_networks[ATT][WifiEnums.SSID_KEY][0]
+        self.dut.droid.wifiRemoveNetworkSuggestions([])
+        self.dut.adb.shell(
+            "pm disable com.google.android.apps.carrier.carrierwifi")
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        self.dut.unlock_screen()
+        self.clear_user_disabled_networks()
+        wutils.wifi_toggle_state(self.dut, True)
+        self.dut.ed.clear_all_events()
+        self.clear_carrier_approved(str(self.dut.droid.telephonyGetSimCarrierId()))
+        if "_ent_" in self.test_name:
+            if "OpenWrtAP" in self.user_params:
+                self.access_points[0].close()
+                self.configure_openwrt_ap_and_start(
+                    ent_network=True,
+                    radius_conf_2g=self.radius_conf_2g,
+                    radius_conf_5g=self.radius_conf_5g,)
+            self.ent_network_2g = self.ent_networks[0]["2g"]
+            self.ent_network_5g = self.ent_networks[0]["5g"]
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        self.dut.droid.wifiRemoveNetworkSuggestions([])
+        self.dut.droid.wifiDisconnect()
+        wutils.reset_wifi(self.dut)
+        wutils.wifi_toggle_state(self.dut, False)
+        self.dut.ed.clear_all_events()
+        self.clear_carrier_approved(str(self.dut.droid.telephonyGetSimCarrierId()))
+
+    def teardown_class(self):
+        self.dut.adb.shell(
+            "pm enable com.google.android.apps.carrier.carrierwifi")
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+    def set_approved(self, approved):
+        self.dut.log.debug("Setting suggestions from sl4a app "
+                           + "approved" if approved else "not approved")
+        self.dut.adb.shell("cmd wifi network-suggestions-set-user-approved"
+                           + " " + SL4A_APK_NAME
+                           + " " + ("yes" if approved else "no"))
+
+    def is_approved(self):
+        is_approved_str = self.dut.adb.shell(
+            "cmd wifi network-suggestions-has-user-approved"
+            + " " + SL4A_APK_NAME)
+        return True if (is_approved_str == "yes") else False
+
+    def set_carrier_approved(self, carrier_id, approved):
+        self.dut.log.debug(("Setting IMSI protection exemption for carrier: " + carrier_id
+                                + "approved" if approved else "not approved"))
+        self.dut.adb.shell("cmd wifi imsi-protection-exemption-set-user-approved-for-carrier"
+                           + " " + carrier_id
+                           + " " + ("yes" if approved else "no"))
+
+    def is_carrier_approved(self, carrier_id):
+        is_approved_str = self.dut.adb.shell(
+            "cmd wifi imsi-protection-exemption-has-user-approved-for-carrier"
+            + " " + carrier_id)
+        return True if (is_approved_str == "yes") else False
+
+    def clear_carrier_approved(self, carrier_id):
+        self.dut.adb.shell(
+            "cmd wifi imsi-protection-exemption-clear-user-approved-for-carrier"
+            + " " + carrier_id)
+
+    def clear_user_disabled_networks(self):
+        self.dut.log.debug("Clearing user disabled networks")
+        self.dut.adb.shell(
+            "cmd wifi clear-user-disabled-networks")
+
+    def add_suggestions_and_ensure_connection(self, network_suggestions,
+                                              expected_ssid,
+                                              expect_post_connection_broadcast):
+        if expect_post_connection_broadcast is not None:
+            self.dut.droid.wifiStartTrackingNetworkSuggestionStateChange()
+
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions(network_suggestions),
+            "Failed to add suggestions")
+        # Enable suggestions by the app.
+        self.dut.log.debug("Enabling suggestions from test")
+        self.set_approved(True)
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        # if suggestion is passpoint wait longer for connection.
+        if "profile" in network_suggestions:
+            time.sleep(PASSPOINT_TIMEOUT)
+        wutils.wait_for_connect(self.dut, expected_ssid)
+
+        if expect_post_connection_broadcast is None:
+            return
+
+        # Check if we expected to get the broadcast.
+        try:
+            event = self.dut.ed.pop_event(
+                wifi_constants.WIFI_NETWORK_SUGGESTION_POST_CONNECTION, 60)
+        except queue.Empty:
+            if expect_post_connection_broadcast:
+                raise signals.TestFailure(
+                    "Did not receive post connection broadcast")
+        else:
+            if not expect_post_connection_broadcast:
+                raise signals.TestFailure(
+                    "Received post connection broadcast")
+        finally:
+            self.dut.droid.wifiStopTrackingNetworkSuggestionStateChange()
+        self.dut.ed.clear_all_events()
+
+    def remove_suggestions_disconnect_and_ensure_no_connection_back(self,
+                                                                    network_suggestions,
+                                                                    expected_ssid):
+        # Remove suggestion trigger disconnect and wait for the disconnect.
+        self.dut.log.info("Removing network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiRemoveNetworkSuggestions(network_suggestions),
+            "Failed to remove suggestions")
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.ed.clear_all_events()
+
+        # Now ensure that we didn't connect back.
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, expected_ssid, assert_on_fail=False),
+            "Device should not connect back")
+
+    def _test_connect_to_wifi_network_reboot_config_store(self,
+                                                          network_suggestions,
+                                                          wifi_network):
+        """ Test network suggestion with reboot config store
+
+        Args:
+        1. network_suggestions: network suggestions in list to add to the device.
+        2. wifi_network: expected wifi network to connect to
+        """
+
+        self.add_suggestions_and_ensure_connection(
+            network_suggestions, wifi_network[WifiEnums.SSID_KEY], None)
+
+        # Reboot and wait for connection back to the same suggestion.
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+
+        wutils.wait_for_connect(self.dut, wifi_network[WifiEnums.SSID_KEY])
+
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            network_suggestions, wifi_network[WifiEnums.SSID_KEY])
+
+        # Reboot with empty suggestion, verify user approval is kept.
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        asserts.assert_true(self.is_approved(), "User approval should be kept")
+
+    @test_tracker_info(uuid="bda8ed20-4382-4380-831a-64cf77eca108")
+    def test_connect_to_wpa_psk_2g(self):
+        """ Adds a network suggestion and ensure that the device connected.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        4. Remove the suggestions and ensure the device does not connect back.
+        """
+        self.add_suggestions_and_ensure_connection(
+            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            False)
+
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="b2df6ebe-9c5b-4e84-906a-e76f96fcef56")
+    def test_connect_to_wpa_psk_2g_with_screen_off(self):
+        """ Adds a network suggestion and ensure that the device connected
+        when the screen is off.
+
+        Steps:
+        1. Send an invalid suggestion to the device (Needed for PNO scan to start).
+        2. Toggle screen off.
+        3. Send a valid network suggestion to the device.
+        4. Wait for the device to connect to it.
+        5. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        6. Remove the suggestions and ensure the device does not connect back.
+        """
+        invalid_suggestion = self.wpa_psk_5g
+        network_ssid = invalid_suggestion.pop(WifiEnums.SSID_KEY)
+        invalid_suggestion[WifiEnums.SSID_KEY] = network_ssid + "blah"
+
+        self.dut.log.info("Adding invalid suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([invalid_suggestion]),
+            "Failed to add suggestions")
+
+        # Approve suggestions by the app.
+        self.set_approved(True)
+
+        # Turn screen off to ensure PNO kicks-in.
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        time.sleep(10)
+
+        # Add valid suggestions & ensure we restart PNO and connect to it.
+        self.add_suggestions_and_ensure_connection(
+            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            False)
+
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="f18bf994-ef3b-45d6-aba0-dd6338b07979")
+    def test_connect_to_wpa_psk_2g_modify_meteredness(self):
+        """ Adds a network suggestion and ensure that the device connected.
+        Change the meteredness of the network after the connection.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        4. Mark the network suggestion metered.
+        5. Ensure that the device disconnected and reconnected back to the
+           suggestion.
+        6. Mark the network suggestion unmetered.
+        7. Ensure that the device did not disconnect.
+        8. Remove the suggestions and ensure the device does not connect back.
+        """
+        self.add_suggestions_and_ensure_connection(
+            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            False)
+
+        mod_suggestion = self.wpa_psk_2g
+
+        # Mark the network metered.
+        self.dut.log.debug("Marking suggestion as metered")
+        mod_suggestion[WifiEnums.IS_SUGGESTION_METERED] = True
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([mod_suggestion]),
+            "Failed to add suggestions")
+        # Wait for disconnect.
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.log.info("Disconnected from network %s", mod_suggestion)
+        self.dut.ed.clear_all_events()
+        # Wait for reconnect.
+        wutils.wait_for_connect(self.dut, mod_suggestion[WifiEnums.SSID_KEY])
+
+        # Mark the network unmetered.
+        self.dut.log.debug("Marking suggestion as unmetered")
+        mod_suggestion[WifiEnums.IS_SUGGESTION_METERED] = False
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([mod_suggestion]),
+            "Failed to add suggestions")
+        # Ensure there is no disconnect.
+        wutils.ensure_no_disconnect(self.dut)
+        self.dut.ed.clear_all_events()
+
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [mod_suggestion], mod_suggestion[WifiEnums.SSID_KEY])
+
+
+    @test_tracker_info(uuid="f54bc250-d9e9-4f00-8b5b-b866e8550b43")
+    def test_connect_to_highest_priority(self):
+        """
+        Adds network suggestions and ensures that device connects to
+        the suggestion with the highest priority.
+
+        Steps:
+        1. Send 2 network suggestions to the device (with different priorities).
+        2. Wait for the device to connect to the network with the highest
+           priority.
+        3. In-place modify network suggestions with priorities reversed
+        4. Restart wifi, wait for the device to connect to the network with the highest
+           priority.
+        5. Re-add the suggestions with the priorities reversed again.
+        6. Again wait for the device to connect to the network with the highest
+           priority.
+        """
+        network_suggestion_2g = self.wpa_psk_2g
+        network_suggestion_5g = self.wpa_psk_5g
+
+        # Add suggestions & wait for the connection event.
+        network_suggestion_2g[WifiEnums.PRIORITY] = 5
+        network_suggestion_5g[WifiEnums.PRIORITY] = 2
+        self.add_suggestions_and_ensure_connection(
+            [network_suggestion_2g, network_suggestion_5g],
+            self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            None)
+
+        # In-place modify Reverse the priority, should be no disconnect
+        network_suggestion_2g[WifiEnums.PRIORITY] = 2
+        network_suggestion_5g[WifiEnums.PRIORITY] = 5
+        self.dut.log.info("Modifying network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion_2g,
+                                                      network_suggestion_5g]),
+            "Failed to add suggestions")
+        wutils.ensure_no_disconnect(self.dut)
+
+        # Disable and re-enable wifi, should connect to higher priority
+        wutils.wifi_toggle_state(self.dut, False)
+        time.sleep(DEFAULT_TIMEOUT)
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        wutils.wait_for_connect(self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [], self.wpa_psk_5g[WifiEnums.SSID_KEY])
+
+        # Reverse the priority.
+        # Add suggestions & wait for the connection event.
+        network_suggestion_2g[WifiEnums.PRIORITY] = 5
+        network_suggestion_5g[WifiEnums.PRIORITY] = 2
+        self.add_suggestions_and_ensure_connection(
+            [network_suggestion_2g, network_suggestion_5g],
+            self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            None)
+
+    @test_tracker_info(uuid="b1d27eea-23c8-4c4f-b944-ef118e4cc35f")
+    def test_connect_to_wpa_psk_2g_with_post_connection_broadcast(self):
+        """ Adds a network suggestion and ensure that the device connected.
+
+        Steps:
+        1. Send a network suggestion to the device with
+           isAppInteractionRequired set.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did receive the post connection broadcast
+           (isAppInteractionRequired = True).
+        4. Remove the suggestions and ensure the device does not connect back.
+        """
+        network_suggestion = self.wpa_psk_2g
+        network_suggestion[WifiEnums.IS_APP_INTERACTION_REQUIRED] = True
+        self.add_suggestions_and_ensure_connection(
+            [network_suggestion], self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            True)
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="a036a24d-29c0-456d-ae6a-afdde34da710")
+    def test_connect_to_wpa_psk_5g_reboot_config_store(self):
+        """
+        Adds a network suggestion and ensure that the device connects to it
+        after reboot.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        4. Reboot the device.
+        5. Wait for the device to connect to back to it.
+        6. Remove the suggestions and ensure the device does not connect back.
+        7. Reboot the device again, ensure user approval is kept
+        """
+        self._test_connect_to_wifi_network_reboot_config_store(
+            [self.wpa_psk_5g], self.wpa_psk_5g)
+
+    @test_tracker_info(uuid="61649a2b-0f00-4272-9b9b-40ad5944da31")
+    def test_connect_to_wpa_ent_config_aka_reboot_config_store(self):
+        """
+        Adds a network suggestion and ensure that the device connects to it
+        after reboot.
+
+        Steps:
+        1. Send a Enterprise AKA network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast.
+        4. Reboot the device.
+        5. Wait for the device to connect to the wifi network.
+        6. Remove suggestions and ensure device doesn't connect back to it.
+        7. Reboot the device again, ensure user approval is kept
+        """
+        self.config_aka = {
+            Ent.EAP: int(EAP.AKA),
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+            "carrierId": str(self.dut.droid.telephonyGetSimCarrierId()),
+        }
+        if "carrierId" in self.config_aka:
+            self.set_carrier_approved(self.config_aka["carrierId"], True)
+        self._test_connect_to_wifi_network_reboot_config_store(
+            [self.config_aka], self.ent_network_2g)
+        if "carrierId" in self.config_aka:
+            self.clear_carrier_approved(self.config_aka["carrierId"])
+
+    @test_tracker_info(uuid="98b2d40a-acb4-4a2f-aba1-b069e2a1d09d")
+    def test_connect_to_wpa_ent_config_ttls_pap_reboot_config_store(self):
+        """
+        Adds a network suggestion and ensure that the device connects to it
+        after reboot.
+
+        Steps:
+        1. Send a Enterprise TTLS PAP network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast.
+        4. Reboot the device.
+        5. Wait for the device to connect to the wifi network.
+        6. Remove suggestions and ensure device doesn't connect back to it.
+        7. Reboot the device again, ensure user approval is kept
+        """
+        self.config_ttls = {
+            Ent.EAP: int(EAP.TTLS),
+            Ent.CA_CERT: self.ca_cert,
+            Ent.IDENTITY: self.eap_identity,
+            Ent.PASSWORD: self.eap_password,
+            Ent.PHASE2: int(EapPhase2.MSCHAPV2),
+            WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+            Ent.ALTSUBJECT_MATCH: self.altsubject_match,
+        }
+        config = dict(self.config_ttls)
+        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
+
+        self._test_connect_to_wifi_network_reboot_config_store(
+            [config], self.ent_network_2g)
+
+    @test_tracker_info(uuid="554b5861-22d0-4922-a5f4-712b4cf564eb")
+    def test_fail_to_connect_to_wpa_psk_5g_when_not_approved(self):
+        """
+        Adds a network suggestion and ensure that the device does not
+        connect to it until we approve the app.
+
+        Steps:
+        1. Send a network suggestion to the device with the app not approved.
+        2. Ensure the network is present in scan results, but we don't connect
+           to it.
+        3. Now approve the app.
+        4. Wait for the device to connect to it.
+        """
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([self.wpa_psk_5g]),
+            "Failed to add suggestions")
+
+        # Disable suggestions by the app.
+        self.set_approved(False)
+
+        # Ensure the app is not approved.
+        asserts.assert_false(
+            self.is_approved(),
+            "Suggestions should be disabled")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+
+        # Ensure we don't connect to the network.
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY], assert_on_fail=False),
+            "Should not connect to network suggestions from unapproved app")
+
+        self.dut.log.info("Enabling suggestions from test")
+        # Now Enable suggestions by the app & ensure we connect to the network.
+        self.set_approved(True)
+
+        # Ensure the app is approved.
+        asserts.assert_true(
+            self.is_approved(),
+            "Suggestions should be enabled")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+
+        wutils.wait_for_connect(self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="98400dea-776e-4a0a-9024-18845b27331c")
+    def test_fail_to_connect_to_wpa_psk_2g_after_user_forgot_network(self):
+        """
+        Adds a network suggestion and ensures that the device does not
+        connect to it after the user forgot the network previously.
+
+        Steps:
+        1. Send a network suggestion to the device with
+           isAppInteractionRequired set.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did receive the post connection broadcast
+           (isAppInteractionRequired = True).
+        4. Simulate user forgetting the network and the device does not
+           connecting back even though the suggestion is active from the app.
+        """
+        network_suggestion = self.wpa_psk_2g
+        network_suggestion[WifiEnums.IS_APP_INTERACTION_REQUIRED] = True
+        self.add_suggestions_and_ensure_connection(
+            [network_suggestion], self.wpa_psk_2g[WifiEnums.SSID_KEY],
+            True)
+
+        # Simulate user disconnect the network.
+        self.dut.droid.wifiUserDisconnectNetwork(
+            self.wpa_psk_2g[WifiEnums.SSID_KEY])
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
+        self.dut.ed.clear_all_events()
+
+        # Now ensure that we don't connect back even though the suggestion
+        # is still active.
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut,
+                                    self.wpa_psk_2g[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False),
+            "Device should not connect back")
+
+    @test_tracker_info(uuid="93c86b05-fa56-4d79-ad27-009a16f691b1")
+    def test_connect_to_hidden_network(self):
+        """
+        Adds a network suggestion with hidden SSID config, ensure device can scan
+        and connect to this network.
+
+        Steps:
+        1. Send a hidden network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        4. Remove the suggestions and ensure the device does not connect back.
+        """
+        asserts.skip_if(not hasattr(self, "hidden_networks"), "No hidden networks, skip this test")
+
+        network_suggestion = self.hidden_network
+        self.add_suggestions_and_ensure_connection(
+            [network_suggestion], network_suggestion[WifiEnums.SSID_KEY], False)
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [network_suggestion], network_suggestion[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="806dff14-7543-482b-bd0a-598de59374b3")
+    def test_connect_to_passpoint_network_with_post_connection_broadcast(self):
+        """ Adds a passpoint network suggestion and ensure that the device connected.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did receive the post connection broadcast
+               (isAppInteractionRequired = true).
+        4. Remove the suggestions and ensure the device does not connect back.
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_network
+        passpoint_config[WifiEnums.IS_APP_INTERACTION_REQUIRED] = True
+        if "carrierId" in passpoint_config:
+            self.set_carrier_approved(passpoint_config["carrierId"], True)
+        self.add_suggestions_and_ensure_connection([passpoint_config],
+                                                   passpoint_config[WifiEnums.SSID_KEY], True)
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [passpoint_config], passpoint_config[WifiEnums.SSID_KEY])
+        if "carrierId" in passpoint_config:
+            self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="159b8b8c-fb00-4d4e-a29f-606881dcbf44")
+    def test_connect_to_passpoint_network_reboot_config_store(self):
+        """
+        Adds a passpoint network suggestion and ensure that the device connects to it
+        after reboot.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        4. Reboot the device.
+        5. Wait for the device to connect to back to it.
+        6. Remove the suggestions and ensure the device does not connect back.
+        7. Reboot the device again, ensure user approval is kept
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_network
+        if "carrierId" in passpoint_config:
+            self.set_carrier_approved(passpoint_config["carrierId"], True)
+        self._test_connect_to_wifi_network_reboot_config_store([passpoint_config],
+                                                               passpoint_config)
+        if "carrierId" in passpoint_config:
+            self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="34f3d28a-bedf-43fe-a12d-2cfadf6bc6eb")
+    def test_fail_to_connect_to_passpoint_network_when_not_approved(self):
+        """
+        Adds a passpoint network suggestion and ensure that the device does not
+        connect to it until we approve the app.
+
+        Steps:
+        1. Send a network suggestion to the device with the app not approved.
+        2. Ensure the network is present in scan results, but we don't connect
+           to it.
+        3. Now approve the app.
+        4. Wait for the device to connect to it.
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_network
+        if "carrierId" in passpoint_config:
+            self.set_carrier_approved(passpoint_config["carrierId"], True)
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([passpoint_config]),
+            "Failed to add suggestions")
+
+        # Disable suggestions by the app.
+        self.set_approved(False)
+
+        # Ensure the app is not approved.
+        asserts.assert_false(
+            self.is_approved(),
+            "Suggestions should be disabled")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+
+        # Ensure we don't connect to the network.
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut, passpoint_config[WifiEnums.SSID_KEY], assert_on_fail=False),
+            "Should not connect to network suggestions from unapproved app")
+
+        self.dut.log.info("Enabling suggestions from test")
+        # Now Enable suggestions by the app & ensure we connect to the network.
+        self.set_approved(True)
+
+        # Ensure the app is approved.
+        asserts.assert_true(
+            self.is_approved(),
+            "Suggestions should be enabled")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        time.sleep(PASSPOINT_TIMEOUT)
+        wutils.wait_for_connect(self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        if "carrierId" in passpoint_config:
+            self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="cf624cda-4d25-42f1-80eb-6c717fb08338")
+    def test_fail_to_connect_to_passpoint_network_when_imsi_protection_exemption_not_approved(self):
+        """
+        Adds a passpoint network suggestion using SIM credential without IMSI privacy protection.
+        Before user approves the exemption, ensure that the device does noconnect to it until we
+        approve the carrier exemption.
+
+        Steps:
+        1. Send a network suggestion to the device with IMSI protection exemption not approved.
+        2. Ensure the network is present in scan results, but we don't connect
+           to it.
+        3. Now approve the carrier.
+        4. Wait for the device to connect to it.
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_network
+        asserts.skip_if("carrierId" not in passpoint_config,
+                        "Not a SIM based passpoint network, skip this test")
+
+        # Ensure the carrier is not approved.
+        asserts.assert_false(
+            self.is_carrier_approved(passpoint_config["carrierId"]),
+            "Carrier shouldn't be approved")
+
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([passpoint_config]),
+            "Failed to add suggestions")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+
+        # Ensure we don't connect to the network.
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut, passpoint_config[WifiEnums.SSID_KEY], assert_on_fail=False),
+            "Should not connect to network suggestions from unapproved app")
+
+        self.dut.log.info("Enabling suggestions from test")
+        # Now approve IMSI protection exemption by carrier & ensure we connect to the network.
+        self.set_carrier_approved(passpoint_config["carrierId"], True)
+
+        # Ensure the carrier is approved.
+        asserts.assert_true(
+            self.is_carrier_approved(passpoint_config["carrierId"]),
+            "Carrier should be approved")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        time.sleep(PASSPOINT_TIMEOUT)
+        wutils.wait_for_connect(self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="e35f99c8-78a4-4b96-9258-f9834b6ddd33")
+    def test_initial_auto_join_on_network_suggestion(self):
+        """
+        Add a network suggestion with enableAutojoin bit set to false, ensure the device doesn't
+        auto connect to this network
+
+        Steps:
+        1. Create a network suggestion.
+        2. Set EnableAutojoin to false.
+        3. Add this suggestion
+        4. Ensure device doesn't connect to his network
+        """
+        network_suggestion = self.wpa_psk_5g
+        # Set suggestion auto join initial to false.
+        network_suggestion[AutoJoin] = False
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion]),
+            "Failed to add suggestions")
+        # Enable suggestions by the app.
+        self.dut.log.debug("Enabling suggestions from test")
+        self.set_approved(True)
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, network_suggestion[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False), "Device should not connect.")
+
+    @test_tracker_info(uuid="ff4e451f-a380-4ff5-a5c2-dd9b1633d5e5")
+    def test_user_override_auto_join_on_network_suggestion(self):
+        """
+        Add a network suggestion, user change the auto join to false, ensure the device doesn't
+        auto connect to this network
+
+        Steps:
+        1. Create a network suggestion.
+        2. Add this suggestion, and ensure we connect to this network
+        3. Simulate user change the auto join to false.
+        4. Toggle the Wifi off and on
+        4. Ensure device doesn't connect to his network
+        """
+        network_suggestion = self.wpa_psk_5g
+        self.add_suggestions_and_ensure_connection([network_suggestion],
+                                                   network_suggestion[WifiEnums.SSID_KEY], False)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        self.dut.log.info(wifi_info)
+        network_id = wifi_info[WifiEnums.NETID_KEY]
+        # Simulate user disable auto join through Settings.
+        self.dut.log.info("Disable auto join on suggestion")
+        self.dut.droid.wifiEnableAutojoin(network_id, False)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, network_suggestion[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False), "Device should not connect.")
+
+    @test_tracker_info(uuid="32201b1c-76a0-46dc-9983-2cd24312a783")
+    def test_untrusted_suggestion_without_untrusted_request(self):
+        """
+        Add an untrusted network suggestion, when no untrusted request, will not connect to it.
+        Steps:
+        1. Create a untrusted network suggestion.
+        2. Add this suggestion, and ensure device do not connect to this network
+        3. Request untrusted network and ensure device connect to this network
+        """
+        network_suggestion = self.open_5g
+        network_suggestion[Untrusted] = True
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion]),
+            "Failed to add suggestions")
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, network_suggestion[WifiEnums.SSID_KEY])
+
+        # Ensure we don't connect to the network.
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut, network_suggestion[WifiEnums.SSID_KEY], assert_on_fail=False),
+            "Should not connect to untrusted network suggestions with no request")
+        network_request = {ClearCapabilities: True, TransportType: 1}
+        req_key = self.dut.droid.connectivityRequestNetwork(network_request)
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, network_suggestion[WifiEnums.SSID_KEY])
+
+        wutils.wait_for_connect(
+            self.dut, network_suggestion[WifiEnums.SSID_KEY], assert_on_fail=False)
+
+        self.dut.droid.connectivityUnregisterNetworkCallback(req_key)
+
diff --git a/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py b/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
new file mode 100644
index 0000000..59d65e7
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
@@ -0,0 +1,515 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts import base_test
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_constants
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+NETWORK_ID_ERROR = "Network don't have ID"
+NETWORK_ERROR = "Device is not connected to reference network"
+
+
+class WifiNewSetupAutoJoinTest(WifiBaseTest):
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def add_network_and_enable(self, network):
+        """Add a network and enable it.
+
+        Args:
+            network : Network details for the network to be added.
+
+        """
+        ret = self.dut.droid.wifiAddNetwork(network)
+        asserts.assert_true(ret != -1, "Add network %r failed" % network)
+        self.dut.droid.wifiEnableNetwork(ret, 0)
+
+    def setup_class(self):
+        """It will setup the required dependencies from config file and configure
+           the required networks for auto-join testing. Configured networks will
+           not be removed. If networks are already configured it will skip
+           configuring the networks
+
+        Returns:
+            True if successfully configured the requirements for testing.
+        """
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ("atten_val", "ping_addr")
+        opt_param = ["reference_networks"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(ap_count=2)
+
+        configured_networks = self.dut.droid.wifiGetConfiguredNetworks()
+        self.log.debug("Configured networks :: {}".format(configured_networks))
+        count_confnet = 0
+        result = False
+        if self.reference_networks[0]['2g']['SSID'] == self.reference_networks[
+                0]['5g']['SSID']:
+            self.ref_ssid_count = 1
+        else:
+            self.ref_ssid_count = 2  # Different SSID for 2g and 5g
+        for confnet in configured_networks:
+            if confnet[WifiEnums.SSID_KEY] == self.reference_networks[0]['2g'][
+                    'SSID']:
+                count_confnet += 1
+            elif confnet[WifiEnums.SSID_KEY] == self.reference_networks[0][
+                    '5g']['SSID']:
+                count_confnet += 1
+        self.log.info("count_confnet {}".format(count_confnet))
+        if count_confnet == self.ref_ssid_count:
+            return
+        else:
+            self.log.info("Configured networks for testing")
+            self.attenuators[0].set_atten(0)
+            self.attenuators[1].set_atten(0)
+            self.attenuators[2].set_atten(90)
+            self.attenuators[3].set_atten(90)
+            wait_time = 15
+            self.dut.droid.wakeLockAcquireBright()
+            self.dut.droid.wakeUpNow()
+            # Add and enable all networks.
+            for network in self.reference_networks:
+                self.add_network_and_enable(network['2g'])
+                self.add_network_and_enable(network['5g'])
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    def check_connection(self, network_bssid):
+        """Check current wifi connection networks.
+        Args:
+            network_bssid: Network bssid to which connection.
+        Returns:
+            True if connection to given network happen, else return False.
+        """
+        time.sleep(40)  #time for connection state to be updated
+        self.log.info("Check network for {}".format(network_bssid))
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.debug("Current network:  {}".format(current_network))
+        if WifiEnums.BSSID_KEY in current_network:
+            return current_network[WifiEnums.BSSID_KEY] == network_bssid
+        return False
+
+    def set_attn_and_validate_connection(self, attn_value, bssid):
+        """Validate wifi connection status on different attenuation setting.
+
+        Args:
+            attn_value: Attenuation value for different APs signal.
+            bssid: Bssid of excepted network.
+
+        Returns:
+            True if bssid of current network match, else false.
+        """
+        self.attenuators[0].set_atten(attn_value[0])
+        self.attenuators[1].set_atten(attn_value[1])
+        self.attenuators[2].set_atten(attn_value[2])
+        self.attenuators[3].set_atten(attn_value[3])
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        try:
+            asserts.assert_true(
+                self.check_connection(bssid),
+                "Device is not connected to required bssid {}".format(bssid))
+            time.sleep(10)  #wait for connection to be active
+            asserts.assert_true(
+                wutils.validate_connection(self.dut, self.ping_addr),
+                "Error, No Internet connection for current bssid {}".format(
+                    bssid))
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    def teardown_class(self):
+        for ad in self.android_devices:
+            wutils.reset_wifi(ad)
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """ Tests Begin """
+
+    """ Test wifi auto join functionality move in range of AP1.
+
+        1. Attenuate the signal to low range of AP1 and Ap2 not visible at all.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="9ea2c78d-d305-497f-87a5-f621f0a4b34e")
+    def test_autojoin_Ap1_2g_AP1_20_AP2_95_AP3_95(self):
+        att0, att1, att2, att3 = self.atten_val["Ap1_2g"]
+        variance = 5
+        attn_value = [att0 + variance * 2, att1, att2, att3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="7c34a508-2ffa-4bca-82b3-9637b7c8250a")
+    def test_autojoin_Ap1_2g_AP1_15_AP2_95_AP3_95(self):
+        att0, att1, att2, att3 = self.atten_val["Ap1_2g"]
+        variance = 5
+        attn_value = [att0 + variance, att1, att2, att3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="ea614fcc-7fca-4172-ba3a-5978427eb40f")
+    def test_autojoin_Ap1_2g_AP1_10_AP2_95_AP3_95(self):
+        att0, att1, att2, att3 = self.atten_val["Ap1_2g"]
+        variance = 5
+        attn_value = [att0, att1, att2, att3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="a1ad25cf-11e7-4240-b3c0-9f14325d5b2d")
+    def test_autojoin_Ap1_2g_AP1_5_AP2_95_AP3_95(self):
+        att0, att1, att2, att3 = self.atten_val["Ap1_2g"]
+        variance = 5
+        attn_value = [att0 - variance, att1, att2, att3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    """ Test wifi auto join functionality move to high range.
+
+        1. Attenuate the signal to high range of AP1.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="b5eba5ec-96e5-4bd8-b483-f5b2a9504558")
+    def test_autojoin_Ap1_2gto5g_AP1_55_AP2_10_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap1_2gto5g"]
+        variance = 5
+        attn_value = [att0 + variance * 2, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["5g"]['bssid'])
+
+    @test_tracker_info(uuid="e63543f7-5f43-4ba2-a5bd-2af3c159a622")
+    def test_autojoin_Ap1_2gto5g_AP1_50_AP2_10_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap1_2gto5g"]
+        variance = 5
+        attn_value = [att0 + variance, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["5g"]['bssid'])
+
+    @test_tracker_info(uuid="0c2cef5d-695d-4d4e-832d-f5e8b393a09c")
+    def test_autojoin_Ap1_2gto5g_AP1_45_AP2_10_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap1_2gto5g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["5g"]['bssid'])
+
+    """ Test wifi auto join functionality move to low range toward AP2.
+
+        1. Attenuate the signal to medium range of AP1 and low range of AP2.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="37822578-d35c-462c-87c0-7a2d9252938c")
+    def test_autojoin_in_AP1_5gto2g_AP1_5_AP2_80_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["In_AP1_5gto2g"]
+        variance = 5
+        attn_value = [att0 - variance, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="194ffe44-9718-4beb-b69e-cccb569f9b81")
+    def test_autojoin_in_AP1_5gto2g_AP1_10_AP2_75_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["In_AP1_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="09bdcb0f-7833-4604-a839-f7d981bf4aca")
+    def test_autojoin_in_AP1_5gto2g_AP1_15_AP2_70_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["In_AP1_5gto2g"]
+        variance = 5
+        attn_value = [att0 + variance, att1 - variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    """ Test wifi auto join functionality move from low range of AP1 to better
+        range of AP2.
+
+        1. Attenuate the signal to low range of AP1 and medium range of AP2.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+            connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="8ffdcab1-2bfb-4acd-b1e8-089ba8a4ec41")
+    def test_autojoin_swtich_AP1toAp2_AP1_65_AP2_75_AP3_2(self):
+        att0, att1, att2, attn3 = self.atten_val["Swtich_AP1toAp2"]
+        variance = 5
+        attn_value = [att0 - variance, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="23e05821-3c53-4033-830e-a446b6105831")
+    def test_autojoin_swtich_AP1toAp2_AP1_70_AP2_70_AP3_2(self):
+        att0, att1, att2, attn3 = self.atten_val["Swtich_AP1toAp2"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="a56ad87d-d37f-4606-9ae8-af6f55cbb6cf")
+    def test_autojoin_swtich_AP1toAp2_AP1_75_AP2_65_AP3_2(self):
+        att0, att1, att2, attn3 = self.atten_val["Swtich_AP1toAp2"]
+        variance = 5
+        attn_value = [att0 + variance, att1 - variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    """ Test wifi auto join functionality move to high range of AP2.
+
+        1. Attenuate the signal to out range of AP1 and high range of AP2.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="7a8b9242-f93c-449a-90a6-4562274a213a")
+    def test_autojoin_Ap2_2gto5g_AP1_70_AP2_85_AP3_75(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2gto5g"]
+        variance = 5
+        attn_value = [att0 - variance, att1 + variance * 2, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["5g"]['bssid'])
+
+    @test_tracker_info(uuid="5e0c5485-a3ae-438a-92e5-9a6b5a22cb82")
+    def test_autojoin_Ap2_2gto5g_AP1_75_AP2_80_AP3_75(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2gto5g"]
+        variance = 5
+        attn_value = [att0, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["5g"]['bssid'])
+
+    @test_tracker_info(uuid="3b289144-a12a-424f-822e-8d173d75c3c3")
+    def test_autojoin_Ap2_2gto5g_AP1_75_AP2_75_AP3_75(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2gto5g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["5g"]['bssid'])
+
+    """ Test wifi auto join functionality move to low range of AP2.
+
+        1. Attenuate the signal to low range of AP2.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable.
+    """
+    @test_tracker_info(uuid="009457df-f430-402c-96ab-c456b043b6f5")
+    def test_autojoin_Ap2_5gto2g_AP1_75_AP2_70_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1 - variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="15ef731c-ddfb-4118-aedb-c177f50bdea0")
+    def test_autojoin_Ap2_5gto2g_AP1_75_AP2_75_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="f79b0570-56a0-43d2-962d-9e114d48bacf")
+    def test_autojoin_Ap2_5gto2g_AP1_75_AP2_80_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="c6d070af-b601-42f1-adec-5ac564154b29")
+    def test_autojoin_out_of_range(self):
+        """Test wifi auto join functionality move to low range.
+
+         1. Attenuate the signal to out of range.
+         2. Wake up the device.
+         3. Start the scan.
+         4. Check that device is not connected to any network.
+        """
+        self.attenuators[0].set_atten(90)
+        self.attenuators[1].set_atten(90)
+        self.attenuators[2].set_atten(90)
+        self.attenuators[3].set_atten(90)
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        try:
+            wutils.start_wifi_connection_scan(self.dut)
+            wifi_results = self.dut.droid.wifiGetScanResults()
+            self.log.debug("Scan result {}".format(wifi_results))
+            time.sleep(20)
+            current_network = self.dut.droid.wifiGetConnectionInfo()
+            self.log.info("Current network: {}".format(current_network))
+            asserts.assert_true(
+                ('network_id' in current_network and
+                 current_network['network_id'] == -1),
+                "Device is connected to network {}".format(current_network))
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    """ Test wifi auto join functionality move in low range of AP2.
+
+        1. Attenuate the signal to move in range of AP2 and Ap1 not visible at all.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="15c27654-bae0-4d2d-bdc8-54fb04b901d1")
+    def test_autojoin_Ap2_2g_AP1_75_AP2_85_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2g"]
+        variance = 5
+        attn_value = [att0, att1 + variance * 2, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="af40824a-4d65-4789-980f-d534abeca36b")
+    def test_autojoin_Ap2_2g_AP1_75_AP2_80_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2g"]
+        variance = 5
+        attn_value = [att0, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="2d482060-9865-472b-810b-c74c6a099e6c")
+    def test_autojoin_Ap2_2g_AP1_75_AP2_75_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="b5cad09e-6e31-40f4-a568-2a1d5271e20c")
+    def test_autojoin_Ap2_2g_AP1_75_AP2_70_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap2_2g"]
+        variance = 5
+        attn_value = [att0, att1 - variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    """ Test wifi auto join functionality move to medium range of Ap2 and
+        low range of AP1.
+
+        1. Attenuate the signal to move in medium range of AP2 and low range of AP1.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="80e74c78-59e2-46db-809d-cb03bd1b6824")
+    def test_autojoin_in_Ap2_5gto2g_AP1_75_AP2_70_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["In_Ap2_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1 - variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="d2a188bd-91cf-4412-a098-739c0c236fe1")
+    def test_autojoin_in_Ap2_5gto2g_AP1_75_AP2_75_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["In_Ap2_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="032e81e9-bc8a-4fa2-a96b-d733c241869e")
+    def test_autojoin_in_Ap2_5gto2g_AP1_75_AP2_80_AP3_10(self):
+        att0, att1, att2, attn3 = self.atten_val["In_Ap2_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[1]["2g"]['bssid'])
+
+    """ Test wifi auto join functionality move from low range of AP2 to better
+        range of AP1.
+
+        1. Attenuate the signal to low range of AP2 and medium range of AP1.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="01faeba0-bd66-4d30-a3d9-b81e959025b2")
+    def test_autojoin_swtich_AP2toAp1_AP1_15_AP2_65_AP3_75(self):
+        att0, att1, att2, attn3 = self.atten_val["Swtich_AP2toAp1"]
+        variance = 5
+        attn_value = [att0 + variance, att1 - variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="68b15c50-03ab-4385-9231-280002315fe5")
+    def test_autojoin_swtich_AP2toAp1_AP1_10_AP2_70_AP3_75(self):
+        att0, att1, att2, attn3 = self.atten_val["Swtich_AP2toAp1"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="1986d79b-097e-44c9-9aff-7bcd56490c3b")
+    def test_autojoin_swtich_AP2toAp1_AP1_5_AP2_75_AP3_75(self):
+        att0, att1, att2, attn3 = self.atten_val["Swtich_AP2toAp1"]
+        variance = 5
+        attn_value = [att0 - variance, att1 + variance, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    """ Test wifi auto join functionality move to medium range of AP1.
+
+        1. Attenuate the signal to medium range of AP1.
+        2. Wake up the device.
+        3. Check that device is connected to right BSSID and maintain stable
+           connection to BSSID in range.
+    """
+    @test_tracker_info(uuid="ec509d40-e339-47c2-995e-cc77f5a28687")
+    def test_autojoin_Ap1_5gto2g_AP1_10_AP2_80_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap1_5gto2g"]
+        variance = 5
+        attn_value = [att0, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="ddc66d1e-3241-4040-996a-85bc2a2a4d67")
+    def test_autojoin_Ap1_5gto2g_AP1_15_AP2_80_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap1_5gto2g"]
+        variance = 5
+        attn_value = [att0 + variance, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    @test_tracker_info(uuid="dfc84504-230f-428e-b701-edc496d0e7b3")
+    def test_autojoin_Ap1_5gto2g_AP1_20_AP2_80_AP3_95(self):
+        att0, att1, att2, attn3 = self.atten_val["Ap1_5gto2g"]
+        variance = 5
+        attn_value = [att0 + variance * 2, att1, att2, attn3]
+        self.set_attn_and_validate_connection(
+            attn_value, self.reference_networks[0]["2g"]['bssid'])
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiPasspointTest.py b/acts_tests/tests/google/wifi/WifiPasspointTest.py
new file mode 100755
index 0000000..4998ac4
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiPasspointTest.py
@@ -0,0 +1,452 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import time
+
+from acts.test_utils.net import ui_utils as uutils
+import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+import WifiManagerTest
+from acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import get_operator_name
+from acts.utils import force_airplane_mode
+
+WifiEnums = wutils.WifiEnums
+
+DEFAULT_TIMEOUT = 10
+OSU_TEST_TIMEOUT = 300
+
+# Constants for providers.
+GLOBAL_RE = 0
+OSU_BOINGO = 0
+BOINGO = 1
+ATT = 2
+
+# Constants used for various device operations.
+RESET = 1
+TOGGLE = 2
+
+UNKNOWN_FQDN = "@#@@!00fffffx"
+
+# Constants for Boingo UI automator
+EDIT_TEXT_CLASS_NAME = "android.widget.EditText"
+PASSWORD_TEXT = "Password"
+PASSPOINT_BUTTON = "Get Passpoint"
+BOINGO_UI_TEXT = "Online Sign Up"
+
+class WifiPasspointTest(WifiBaseTest):
+    """Tests for APIs in Android's WifiManager class.
+
+    Test Bed Requirement:
+    * One Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+
+    def setup_class(self):
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ["passpoint_networks",
+                      "boingo_username",
+                      "boingo_password",]
+        self.unpack_userparams(req_param_names=req_params,)
+        asserts.assert_true(
+            len(self.passpoint_networks) > 0,
+            "Need at least one Passpoint network.")
+        wutils.wifi_toggle_state(self.dut, True)
+        self.unknown_fqdn = UNKNOWN_FQDN
+
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        self.dut.unlock_screen()
+
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        passpoint_configs = self.dut.droid.getPasspointConfigs()
+        for config in passpoint_configs:
+            wutils.delete_passpoint(self.dut, config)
+        wutils.reset_wifi(self.dut)
+
+
+    """Helper Functions"""
+
+
+    def install_passpoint_profile(self, passpoint_config):
+        """Install the Passpoint network Profile.
+
+        Args:
+            passpoint_config: A JSON dict of the Passpoint configuration.
+
+        """
+        asserts.assert_true(WifiEnums.SSID_KEY in passpoint_config,
+                "Key '%s' must be present in network definition." %
+                WifiEnums.SSID_KEY)
+        # Install the Passpoint profile.
+        self.dut.droid.addUpdatePasspointConfig(passpoint_config)
+
+
+    def check_passpoint_connection(self, passpoint_network):
+        """Verify the device is automatically able to connect to the Passpoint
+           network.
+
+           Args:
+               passpoint_network: SSID of the Passpoint network.
+
+        """
+        ad = self.dut
+        ad.ed.clear_all_events()
+        try:
+            wutils.start_wifi_connection_scan_and_return_status(ad)
+            wutils.wait_for_connect(ad)
+        except:
+            pass
+        # Re-verify we are connected to the correct network.
+        network_info = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Network Info: %s" % network_info)
+        if not network_info or not network_info[WifiEnums.SSID_KEY] or \
+            network_info[WifiEnums.SSID_KEY] not in passpoint_network:
+              raise signals.TestFailure(
+                  "Device did not connect to passpoint network.")
+
+
+    def get_configured_passpoint_and_delete(self):
+        """Get configured Passpoint network and delete using its FQDN."""
+        passpoint_config = self.dut.droid.getPasspointConfigs()
+        if not len(passpoint_config):
+            raise signals.TestFailure("Failed to fetch the list of configured"
+                                      "passpoint networks.")
+        if not wutils.delete_passpoint(self.dut, passpoint_config[0]):
+            raise signals.TestFailure("Failed to delete Passpoint configuration"
+                                      " with FQDN = %s" % passpoint_config[0])
+
+    def ui_automator_boingo(self):
+        """Run UI automator for boingo passpoint."""
+        # Verify the boingo login page shows
+        asserts.assert_true(
+            uutils.has_element(self.dut, text=BOINGO_UI_TEXT),
+            "Failed to launch boingohotspot login page")
+
+        # Go to the bottom of the page
+        for _ in range(3):
+            self.dut.adb.shell("input swipe 300 900 300 300")
+
+        # Enter username
+        uutils.wait_and_input_text(self.dut,
+                                   input_text=self.boingo_username,
+                                   text="",
+                                   class_name=EDIT_TEXT_CLASS_NAME)
+        self.dut.adb.shell("input keyevent 111")  # collapse keyboard
+        self.dut.adb.shell("input swipe 300 900 300 750")  # swipe up to show text
+
+        # Enter password
+        uutils.wait_and_input_text(self.dut,
+                                   input_text=self.boingo_password,
+                                   text=PASSWORD_TEXT)
+        self.dut.adb.shell("input keyevent 111")  # collapse keyboard
+        self.dut.adb.shell("input swipe 300 900 300 750")  # swipe up to show text
+
+        # Login
+        uutils.wait_and_click(self.dut, text=PASSPOINT_BUTTON)
+
+
+    def start_subscription_provisioning(self, state):
+        """Start subscription provisioning with a default provider."""
+
+        self.unpack_userparams(('osu_configs',))
+        asserts.assert_true(
+            len(self.osu_configs) > 0,
+            "Need at least one osu config.")
+        osu_config = self.osu_configs[OSU_BOINGO]
+        # Clear all previous events.
+        self.dut.ed.clear_all_events()
+        self.dut.droid.startSubscriptionProvisioning(osu_config)
+        start_time = time.time()
+        while time.time() < start_time + OSU_TEST_TIMEOUT:
+            dut_event = self.dut.ed.pop_event("onProvisioningCallback",
+                                              DEFAULT_TIMEOUT * 18)
+            if dut_event['data']['tag'] == 'success':
+                self.log.info("Passpoint Provisioning Success")
+                # Reset WiFi after provisioning success.
+                if state == RESET:
+                    wutils.reset_wifi(self.dut)
+                    time.sleep(DEFAULT_TIMEOUT)
+                # Toggle WiFi after provisioning success.
+                elif state == TOGGLE:
+                    wutils.toggle_wifi_off_and_on(self.dut)
+                    time.sleep(DEFAULT_TIMEOUT)
+                break
+            if dut_event['data']['tag'] == 'failure':
+                raise signals.TestFailure(
+                    "Passpoint Provisioning is failed with %s" %
+                    dut_event['data'][
+                        'reason'])
+                break
+            if dut_event['data']['tag'] == 'status':
+                self.log.info(
+                    "Passpoint Provisioning status %s" % dut_event['data'][
+                        'status'])
+                if int(dut_event['data']['status']) == 7:
+                    time.sleep(DEFAULT_TIMEOUT)
+                    self.ui_automator_boingo()
+        # Clear all previous events.
+        self.dut.ed.clear_all_events()
+
+        # Verify device connects to the Passpoint network.
+        time.sleep(DEFAULT_TIMEOUT)
+        current_passpoint = self.dut.droid.wifiGetConnectionInfo()
+        if current_passpoint[WifiEnums.SSID_KEY] not in osu_config[
+            "expected_ssids"]:
+            raise signals.TestFailure("Device did not connect to the %s"
+                                      " passpoint network" % osu_config[
+                                          "expected_ssids"])
+        # Delete the Passpoint profile.
+        self.get_configured_passpoint_and_delete()
+        wutils.wait_for_disconnect(self.dut)
+
+
+    """Tests"""
+
+    @test_tracker_info(uuid="b0bc0153-77bb-4594-8f19-cea2c6bd2f43")
+    def test_add_passpoint_network(self):
+        """Add a Passpoint network and verify device connects to it.
+
+        Steps:
+            1. Install a Passpoint Profile.
+            2. Verify the device connects to the required Passpoint SSID.
+            3. Get the Passpoint configuration added above.
+            4. Delete Passpoint configuration using its FQDN.
+            5. Verify that we are disconnected from the Passpoint network.
+
+        """
+        passpoint_config = self.passpoint_networks[BOINGO]
+        self.install_passpoint_profile(passpoint_config)
+        ssid = passpoint_config[WifiEnums.SSID_KEY]
+        self.check_passpoint_connection(ssid)
+        self.get_configured_passpoint_and_delete()
+        wutils.wait_for_disconnect(self.dut)
+
+
+    @test_tracker_info(uuid="eb29d6e2-a755-4c9c-9e4e-63ea2277a64a")
+    def test_update_passpoint_network(self):
+        """Update a previous Passpoint network and verify device still connects
+           to it.
+
+        1. Install a Passpoint Profile.
+        2. Verify the device connects to the required Passpoint SSID.
+        3. Update the Passpoint Profile.
+        4. Verify device is still connected to the Passpoint SSID.
+        5. Get the Passpoint configuration added above.
+        6. Delete Passpoint configuration using its FQDN.
+
+        """
+        passpoint_config = self.passpoint_networks[BOINGO]
+        self.install_passpoint_profile(passpoint_config)
+        ssid = passpoint_config[WifiEnums.SSID_KEY]
+        self.check_passpoint_connection(ssid)
+
+        # Update passpoint configuration using the original profile because we
+        # do not have real profile with updated credentials to use.
+        self.install_passpoint_profile(passpoint_config)
+
+        # Wait for a Disconnect event from the supplicant.
+        wutils.wait_for_disconnect(self.dut)
+
+        # Now check if we are again connected with the updated profile.
+        self.check_passpoint_connection(ssid)
+
+        self.get_configured_passpoint_and_delete()
+        wutils.wait_for_disconnect(self.dut)
+
+
+    @test_tracker_info(uuid="b6e8068d-faa1-49f2-b421-c60defaed5f0")
+    def test_add_delete_list_of_passpoint_network(self):
+        """Add multiple passpoint networks, list them and delete one by one.
+
+        1. Install Passpoint Profile A.
+        2. Install Passpoint Profile B.
+        3. Get all the Passpoint configurations added above and verify.
+        6. Ensure all Passpoint configurations can be deleted.
+
+        """
+        for passpoint_config in self.passpoint_networks[:2]:
+            self.install_passpoint_profile(passpoint_config)
+            time.sleep(DEFAULT_TIMEOUT)
+        configs = self.dut.droid.getPasspointConfigs()
+        #  It is length -1 because ATT profile will be handled separately
+        if not len(configs) or len(configs) != len(self.passpoint_networks[:2]):
+            raise signals.TestFailure("Failed to fetch some or all of the"
+                                      " configured passpoint networks.")
+        for config in configs:
+            if not wutils.delete_passpoint(self.dut, config):
+                raise signals.TestFailure("Failed to delete Passpoint"
+                                          " configuration with FQDN = %s" %
+                                          config)
+
+
+    @test_tracker_info(uuid="a53251be-7aaf-41fc-a5f3-63984269d224")
+    def test_delete_unknown_fqdn(self):
+        """Negative test to delete Passpoint profile using an unknown FQDN.
+
+        1. Pass an unknown FQDN for removal.
+        2. Verify that it was not successful.
+
+        """
+        if wutils.delete_passpoint(self.dut, self.unknown_fqdn):
+            raise signals.TestFailure("Failed because an unknown FQDN"
+                                      " was successfully deleted.")
+
+
+    @test_tracker_info(uuid="bf03c03a-e649-4e2b-a557-1f791bd98951")
+    def test_passpoint_failover(self):
+        """Add a pair of passpoint networks and test failover when one of the"
+           profiles is removed.
+
+        1. Install a Passpoint Profile A and B.
+        2. Verify device connects to a Passpoint network and get SSID.
+        3. Delete the current Passpoint profile using its FQDN.
+        4. Verify device fails over and connects to the other Passpoint SSID.
+        5. Delete Passpoint configuration using its FQDN.
+
+        """
+        # Install both Passpoint profiles on the device.
+        passpoint_ssid = list()
+        for passpoint_config in self.passpoint_networks[:2]:
+            passpoint_ssid.extend(passpoint_config[WifiEnums.SSID_KEY])
+            self.install_passpoint_profile(passpoint_config)
+            time.sleep(DEFAULT_TIMEOUT)
+
+        # Get the current network and the failover network.
+        wutils.wait_for_connect(self.dut)
+        current_passpoint = self.dut.droid.wifiGetConnectionInfo()
+        current_ssid = current_passpoint[WifiEnums.SSID_KEY]
+        if current_ssid not in passpoint_ssid:
+           raise signals.TestFailure("Device did not connect to any of the "
+                                     "configured Passpoint networks.")
+
+        expected_ssid =  self.passpoint_networks[0][WifiEnums.SSID_KEY]
+        if current_ssid in expected_ssid:
+            expected_ssid = self.passpoint_networks[1][WifiEnums.SSID_KEY]
+
+        # Remove the current Passpoint profile.
+        for network in self.passpoint_networks[:2]:
+            if current_ssid in network[WifiEnums.SSID_KEY]:
+                if not wutils.delete_passpoint(self.dut, network["fqdn"]):
+                    raise signals.TestFailure("Failed to delete Passpoint"
+                                              " configuration with FQDN = %s" %
+                                              network["fqdn"])
+        # Verify device fails over and connects to the other passpoint network.
+        time.sleep(DEFAULT_TIMEOUT)
+
+        current_passpoint = self.dut.droid.wifiGetConnectionInfo()
+        if current_passpoint[WifiEnums.SSID_KEY] not in expected_ssid:
+            raise signals.TestFailure("Device did not failover to the %s"
+                                      " passpoint network" % expected_ssid)
+
+        # Delete the remaining Passpoint profile.
+        self.get_configured_passpoint_and_delete()
+        wutils.wait_for_disconnect(self.dut)
+
+
+    @test_tracker_info(uuid="37ae0223-0cb7-43f3-8ba8-474fad6e4b71")
+    def test_install_att_passpoint_profile(self):
+        """Add an AT&T Passpoint profile.
+
+        It is used for only installing the profile for other tests.
+        """
+        isFound = False
+        for passpoint_config in self.passpoint_networks:
+            if 'att' in passpoint_config['fqdn']:
+                isFound = True
+                self.install_passpoint_profile(passpoint_config)
+                break
+        if not isFound:
+            raise signals.TestFailure("cannot find ATT profile.")
+
+
+    @test_tracker_info(uuid="e3e826d2-7c39-4c37-ab3f-81992d5aa0e8")
+    def test_att_passpoint_network(self):
+        """Add a AT&T Passpoint network and verify device connects to it.
+
+        Steps:
+            1. Install a AT&T Passpoint Profile.
+            2. Verify the device connects to the required Passpoint SSID.
+            3. Get the Passpoint configuration added above.
+            4. Delete Passpoint configuration using its FQDN.
+            5. Verify that we are disconnected from the Passpoint network.
+
+        """
+        carriers = ["att"]
+        operator = get_operator_name(self.log, self.dut)
+        asserts.skip_if(operator not in carriers,
+                        "Device %s does not have a ATT sim" % self.dut.model)
+
+        passpoint_config = self.passpoint_networks[ATT]
+        self.install_passpoint_profile(passpoint_config)
+        ssid = passpoint_config[WifiEnums.SSID_KEY]
+        self.check_passpoint_connection(ssid)
+        self.get_configured_passpoint_and_delete()
+        wutils.wait_for_disconnect(self.dut)
+
+
+    @test_tracker_info(uuid="c85c81b2-7133-4635-8328-9498169ae802")
+    def test_start_subscription_provisioning(self):
+        self.start_subscription_provisioning(0)
+
+
+    @test_tracker_info(uuid="fd09a643-0d4b-45a9-881a-a771f9707ab1")
+    def test_start_subscription_provisioning_and_reset_wifi(self):
+        self.start_subscription_provisioning(RESET)
+
+
+    @test_tracker_info(uuid="f43ea759-673f-4567-aa11-da3bc2cabf08")
+    def test_start_subscription_provisioning_and_toggle_wifi(self):
+        self.start_subscription_provisioning(TOGGLE)
+
+    @test_tracker_info(uuid="ad6d5eb8-a3c5-4ce0-9e10-d0f201cd0f40")
+    def test_user_override_auto_join_on_passpoint_network(self):
+        """Add a Passpoint network, simulate user change the auto join to false, ensure the device
+        doesn't auto connect to this passponit network
+
+        Steps:
+            1. Install a Passpoint Profile.
+            2. Verify the device connects to the required Passpoint SSID.
+            3. Disable auto join Passpoint configuration using its FQDN.
+            4. disable and enable Wifi toggle, ensure we don't connect back
+        """
+        passpoint_config = self.passpoint_networks[BOINGO]
+        self.install_passpoint_profile(passpoint_config)
+        ssid = passpoint_config[WifiEnums.SSID_KEY]
+        self.check_passpoint_connection(ssid)
+        self.dut.log.info("Disable auto join on passpoint")
+        self.dut.droid.wifiEnableAutojoinPasspoint(passpoint_config['fqdn'], False)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, ssid, assert_on_fail=False),
+            "Device should not connect.")
diff --git a/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py b/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
new file mode 100644
index 0000000..97d9374
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+
+
+class WifiPerformancePreflightTest(base_test.BaseTestClass):
+    """Class for Wifi performance preflight tests.
+
+    This class implements WiFi performance tests to perform before any other
+    test suite. Currently, the preflight checklist checks the wifi firmware and
+    config files, i.e., bdf files for any changes by retrieving their version
+    number and checksum.
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+
+    def setup_class(self):
+        self.dut = self.android_devices[-1]
+        # Initialize AP to ensure that tests can be run in later suites
+        req_params = ['RetailAccessPoints']
+        opt_params = ['bdf', 'firmware']
+        self.unpack_userparams(req_params, opt_params)
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        # Load BDF and firmware if needed
+        if hasattr(self, 'bdf'):
+            self.log.info('Pushing WiFi BDF to DUT.')
+            wputils.push_config(self.dut, self.bdf[0])
+        if hasattr(self, 'firmware'):
+            self.log.info('Pushing WiFi firmware to DUT.')
+            wputils.push_firmware(self.dut, self.firmware)
+
+        for ad in self.android_devices:
+            ad.droid.wifiEnableVerboseLogging(1)
+            ad.adb.shell("wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
+                         "wlan0 log_level EXCESSIVE")
+
+    def test_wifi_sw_signature(self):
+        sw_signature = wputils.get_sw_signature(self.dut)
+        self.testcase_metric_logger.add_metric(
+            'config_signature', sw_signature['config_signature'])
+        self.testcase_metric_logger.add_metric('fw_signature',
+                                               sw_signature['fw_signature'])
+        self.testcase_metric_logger.add_metric('serial_hash',
+                                               sw_signature['serial_hash'])
+
+    def teardown_class(self):
+        # Teardown AP and release its lockfile
+        self.access_point.teardown()
diff --git a/acts_tests/tests/google/wifi/WifiPingTest.py b/acts_tests/tests/google/wifi/WifiPingTest.py
new file mode 100644
index 0000000..1df52d5
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiPingTest.py
@@ -0,0 +1,802 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import itertools
+import json
+import logging
+import os
+import statistics
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.controllers.utils_lib import ssh
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import ota_sniffer
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from functools import partial
+
+
+class WifiPingTest(base_test.BaseTestClass):
+    """Class for ping-based Wifi performance tests.
+
+    This class implements WiFi ping performance tests such as range and RTT.
+    The class setups up the AP in the desired configurations, configures
+    and connects the phone to the AP, and runs  For an example config file to
+    run this test class see example_connectivity_performance_ap_sta.json.
+    """
+
+    TEST_TIMEOUT = 10
+    RSSI_POLL_INTERVAL = 0.2
+    SHORT_SLEEP = 1
+    MED_SLEEP = 5
+    MAX_CONSECUTIVE_ZEROS = 5
+    DISCONNECTED_PING_RESULT = {
+        'connected': 0,
+        'rtt': [],
+        'time_stamp': [],
+        'ping_interarrivals': [],
+        'packet_loss_percentage': 100
+    }
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        self.dut = self.android_devices[-1]
+        req_params = [
+            'ping_test_params', 'testbed_params', 'main_network',
+            'RetailAccessPoints', 'RemoteServer'
+        ]
+        opt_params = ['OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        self.testclass_params = self.ping_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.ping_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        self.atten_dut_chain_map = {}
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                                "Can not turn on airplane mode.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+        # Configure test retries
+        self.user_params['retry_tests'] = [self.__class__.__name__]
+
+    def teardown_class(self):
+        # Turn WiFi OFF and reset AP
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+        self.process_testclass_results()
+
+    def setup_test(self):
+        self.retry_flag = False
+
+    def teardown_test(self):
+        self.retry_flag = False
+
+    def on_retry(self):
+        """Function to control test logic on retried tests.
+
+        This function is automatically executed on tests that are being
+        retried. In this case the function resets wifi, toggles it off and on
+        and sets a retry_flag to enable further tweaking the test logic on
+        second attempts.
+        """
+        self.retry_flag = True
+        for dev in self.android_devices:
+            wutils.reset_wifi(dev)
+            wutils.toggle_wifi_off_and_on(dev)
+
+    def process_testclass_results(self):
+        """Saves all test results to enable comparison."""
+        testclass_summary = {}
+        for test in self.testclass_results:
+            if 'range' in test['test_name']:
+                testclass_summary[test['test_name']] = test['range']
+        # Save results
+        results_file_path = os.path.join(self.log_path,
+                                         'testclass_summary.json')
+        with open(results_file_path, 'w') as results_file:
+            json.dump(testclass_summary, results_file, indent=4)
+
+    def pass_fail_check_ping_rtt(self, result):
+        """Check the test result and decide if it passed or failed.
+
+        The function computes RTT statistics and fails any tests in which the
+        tail of the ping latency results exceeds the threshold defined in the
+        configuration file.
+
+        Args:
+            result: dict containing ping results and other meta data
+        """
+        ignored_fraction = (self.testclass_params['rtt_ignored_interval'] /
+                            self.testclass_params['rtt_ping_duration'])
+        sorted_rtt = [
+            sorted(x['rtt'][round(ignored_fraction * len(x['rtt'])):])
+            for x in result['ping_results']
+        ]
+        disconnected = any([len(x) == 0 for x in sorted_rtt])
+        if disconnected:
+            asserts.fail('Test failed. DUT disconnected at least once.')
+
+        rtt_at_test_percentile = [
+            x[int((1 - self.testclass_params['rtt_test_percentile'] / 100) *
+                  len(x))] for x in sorted_rtt
+        ]
+        # Set blackbox metric
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric('ping_rtt',
+                                                   max(rtt_at_test_percentile))
+        # Evaluate test pass/fail
+        rtt_failed = any([
+            rtt > self.testclass_params['rtt_threshold'] * 1000
+            for rtt in rtt_at_test_percentile
+        ])
+        if rtt_failed:
+            #TODO: figure out how to cleanly exclude RTT tests from retry
+            asserts.explicit_pass(
+                'Test failed. RTTs at test percentile = {}'.format(
+                    rtt_at_test_percentile))
+        else:
+            asserts.explicit_pass(
+                'Test Passed. RTTs at test percentile = {}'.format(
+                    rtt_at_test_percentile))
+
+    def pass_fail_check_ping_range(self, result):
+        """Check the test result and decide if it passed or failed.
+
+        Checks whether the attenuation at which ping packet losses begin to
+        exceed the threshold matches the range derived from golden
+        rate-vs-range result files. The test fails is ping range is
+        range_gap_threshold worse than RvR range.
+
+        Args:
+            result: dict containing ping results and meta data
+        """
+        # Evaluate test pass/fail
+        test_message = ('Attenuation at range is {}dB. '
+                        'LLStats at Range: {}'.format(
+                            result['range'], result['llstats_at_range']))
+        if result['peak_throughput_pct'] < 95:
+            asserts.fail("(RESULT NOT RELIABLE) {}".format(test_message))
+
+        # If pass, set Blackbox metric
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric('ping_range',
+                                                   result['range'])
+        asserts.explicit_pass(test_message)
+
+    def pass_fail_check(self, result):
+        if 'range' in result['testcase_params']['test_type']:
+            self.pass_fail_check_ping_range(result)
+        else:
+            self.pass_fail_check_ping_rtt(result)
+
+    def process_ping_results(self, testcase_params, ping_range_result):
+        """Saves and plots ping results.
+
+        Args:
+            ping_range_result: dict containing ping results and metadata
+        """
+        # Compute range
+        ping_loss_over_att = [
+            x['packet_loss_percentage']
+            for x in ping_range_result['ping_results']
+        ]
+        ping_loss_above_threshold = [
+            x > self.testclass_params['range_ping_loss_threshold']
+            for x in ping_loss_over_att
+        ]
+        for idx in range(len(ping_loss_above_threshold)):
+            if all(ping_loss_above_threshold[idx:]):
+                range_index = max(idx, 1) - 1
+                break
+        else:
+            range_index = -1
+        ping_range_result['atten_at_range'] = testcase_params['atten_range'][
+            range_index]
+        ping_range_result['peak_throughput_pct'] = 100 - min(
+            ping_loss_over_att)
+        ping_range_result['range'] = (ping_range_result['atten_at_range'] +
+                                      ping_range_result['fixed_attenuation'])
+        ping_range_result['llstats_at_range'] = (
+            'TX MCS = {0} ({1:.1f}%). '
+            'RX MCS = {2} ({3:.1f}%)'.format(
+                ping_range_result['llstats'][range_index]['summary']
+                ['common_tx_mcs'], ping_range_result['llstats'][range_index]
+                ['summary']['common_tx_mcs_freq'] * 100,
+                ping_range_result['llstats'][range_index]['summary']
+                ['common_rx_mcs'], ping_range_result['llstats'][range_index]
+                ['summary']['common_rx_mcs_freq'] * 100))
+
+        # Save results
+        results_file_path = os.path.join(
+            self.log_path, '{}.json'.format(self.current_test_name))
+        with open(results_file_path, 'w') as results_file:
+            json.dump(ping_range_result, results_file, indent=4)
+
+        # Plot results
+        if 'range' not in self.current_test_name:
+            figure = wputils.BokehFigure(
+                self.current_test_name,
+                x_label='Timestamp (s)',
+                primary_y_label='Round Trip Time (ms)')
+            for idx, result in enumerate(ping_range_result['ping_results']):
+                if len(result['rtt']) > 1:
+                    x_data = [
+                        t - result['time_stamp'][0]
+                        for t in result['time_stamp']
+                    ]
+                    figure.add_line(
+                        x_data, result['rtt'], 'RTT @ {}dB'.format(
+                            ping_range_result['attenuation'][idx]))
+
+            output_file_path = os.path.join(
+                self.log_path, '{}.html'.format(self.current_test_name))
+            figure.generate_figure(output_file_path)
+
+    def run_ping_test(self, testcase_params):
+        """Main function to test ping.
+
+        The function sets up the AP in the correct channel and mode
+        configuration and calls get_ping_stats while sweeping attenuation
+
+        Args:
+            testcase_params: dict containing all test parameters
+        Returns:
+            test_result: dict containing ping results and other meta data
+        """
+        # Prepare results dict
+        llstats_obj = wputils.LinkLayerStats(
+            self.dut, self.testclass_params.get('llstats_enabled', True))
+        test_result = collections.OrderedDict()
+        test_result['testcase_params'] = testcase_params.copy()
+        test_result['test_name'] = self.current_test_name
+        test_result['ap_config'] = self.access_point.ap_settings.copy()
+        test_result['attenuation'] = testcase_params['atten_range']
+        test_result['fixed_attenuation'] = self.testbed_params[
+            'fixed_attenuation'][str(testcase_params['channel'])]
+        test_result['rssi_results'] = []
+        test_result['ping_results'] = []
+        test_result['llstats'] = []
+        # Setup sniffer
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.start_capture(
+                testcase_params['test_network'],
+                chan=int(testcase_params['channel']),
+                bw=int(testcase_params['mode'][3:]),
+                duration=testcase_params['ping_duration'] *
+                len(testcase_params['atten_range']) + self.TEST_TIMEOUT)
+        # Run ping and sweep attenuation as needed
+        zero_counter = 0
+        for atten in testcase_params['atten_range']:
+            for attenuator in self.attenuators:
+                attenuator.set_atten(atten, strict=False)
+            rssi_future = wputils.get_connected_rssi_nb(
+                self.dut,
+                int(testcase_params['ping_duration'] / 2 /
+                    self.RSSI_POLL_INTERVAL), self.RSSI_POLL_INTERVAL,
+                testcase_params['ping_duration'] / 2)
+            # Refresh link layer stats
+            llstats_obj.update_stats()
+            current_ping_stats = wputils.get_ping_stats(
+                self.ping_server, self.dut_ip,
+                testcase_params['ping_duration'],
+                testcase_params['ping_interval'], testcase_params['ping_size'])
+            current_rssi = rssi_future.result()
+            test_result['rssi_results'].append(current_rssi)
+            llstats_obj.update_stats()
+            curr_llstats = llstats_obj.llstats_incremental.copy()
+            test_result['llstats'].append(curr_llstats)
+            if current_ping_stats['connected']:
+                self.log.info(
+                    'Attenuation = {0}dB\tPacket Loss = {1}%\t'
+                    'Avg RTT = {2:.2f}ms\tRSSI = {3} [{4},{5}]\t'.format(
+                        atten, current_ping_stats['packet_loss_percentage'],
+                        statistics.mean(current_ping_stats['rtt']),
+                        current_rssi['signal_poll_rssi']['mean'],
+                        current_rssi['chain_0_rssi']['mean'],
+                        current_rssi['chain_1_rssi']['mean']))
+                if current_ping_stats['packet_loss_percentage'] == 100:
+                    zero_counter = zero_counter + 1
+                else:
+                    zero_counter = 0
+            else:
+                self.log.info(
+                    'Attenuation = {}dB. Disconnected.'.format(atten))
+                zero_counter = zero_counter + 1
+            test_result['ping_results'].append(current_ping_stats.as_dict())
+            if zero_counter == self.MAX_CONSECUTIVE_ZEROS:
+                self.log.info('Ping loss stable at 100%. Stopping test now.')
+                for idx in range(
+                        len(testcase_params['atten_range']) -
+                        len(test_result['ping_results'])):
+                    test_result['ping_results'].append(
+                        self.DISCONNECTED_PING_RESULT)
+                break
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.stop_capture()
+        return test_result
+
+    def setup_ap(self, testcase_params):
+        """Sets up the access point in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '2G' in band:
+            frequency = wutils.WifiEnums.channel_2G_to_freq[
+                testcase_params['channel']]
+        else:
+            frequency = wutils.WifiEnums.channel_5G_to_freq[
+                testcase_params['channel']]
+        if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
+            self.access_point.set_region(self.testbed_params['DFS_region'])
+        else:
+            self.access_point.set_region(self.testbed_params['default_region'])
+        self.access_point.set_channel(band, testcase_params['channel'])
+        self.access_point.set_bandwidth(band, testcase_params['mode'])
+        if 'low' in testcase_params['ap_power']:
+            self.log.info('Setting low AP power.')
+            self.access_point.set_power(
+                band, self.testclass_params['low_ap_tx_power'])
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('Battery level too low. Skipping test.')
+        # Turn screen off to preserve battery
+        self.dut.go_to_sleep()
+        if wputils.validate_network(self.dut,
+                                    testcase_params['test_network']['SSID']):
+            self.log.info('Already connected to desired network')
+        else:
+            wutils.reset_wifi(self.dut)
+            wutils.set_wifi_country_code(self.dut,
+                                         self.testclass_params['country_code'])
+            testcase_params['test_network']['channel'] = testcase_params[
+                'channel']
+            wutils.wifi_connect(self.dut,
+                                testcase_params['test_network'],
+                                num_of_tries=5,
+                                check_connectivity=True)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
+            self.atten_dut_chain_map[testcase_params[
+                'channel']] = wputils.get_current_atten_dut_chain_map(
+                    self.attenuators, self.dut, self.ping_server)
+        self.log.info("Current Attenuator-DUT Chain Map: {}".format(
+            self.atten_dut_chain_map[testcase_params['channel']]))
+        for idx, atten in enumerate(self.attenuators):
+            if self.atten_dut_chain_map[testcase_params['channel']][
+                    idx] == testcase_params['attenuated_chain']:
+                atten.offset = atten.instrument.max_atten
+            else:
+                atten.offset = 0
+
+    def setup_ping_test(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Configure AP
+        self.setup_ap(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False)
+        # Reset, configure, and connect DUT
+        self.setup_dut(testcase_params)
+
+    def get_range_start_atten(self, testcase_params):
+        """Gets the starting attenuation for this ping test.
+
+        This function is used to get the starting attenuation for ping range
+        tests. This implementation returns the default starting attenuation,
+        however, defining this function enables a more involved configuration
+        for over-the-air test classes.
+
+        Args:
+            testcase_params: dict containing all test params
+        """
+        return self.testclass_params['range_atten_start']
+
+    def compile_test_params(self, testcase_params):
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[band]
+        if testcase_params['chain_mask'] in ['0', '1']:
+            testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
+                1 if testcase_params['chain_mask'] == '0' else 0)
+        else:
+            # Set attenuated chain to -1. Do not set to None as this will be
+            # compared to RF chain map which may include None
+            testcase_params['attenuated_chain'] = -1
+        if testcase_params['test_type'] == 'test_ping_range':
+            testcase_params.update(
+                ping_interval=self.testclass_params['range_ping_interval'],
+                ping_duration=self.testclass_params['range_ping_duration'],
+                ping_size=self.testclass_params['ping_size'],
+            )
+        elif testcase_params['test_type'] == 'test_fast_ping_rtt':
+            testcase_params.update(
+                ping_interval=self.testclass_params['rtt_ping_interval']
+                ['fast'],
+                ping_duration=self.testclass_params['rtt_ping_duration'],
+                ping_size=self.testclass_params['ping_size'],
+            )
+        elif testcase_params['test_type'] == 'test_slow_ping_rtt':
+            testcase_params.update(
+                ping_interval=self.testclass_params['rtt_ping_interval']
+                ['slow'],
+                ping_duration=self.testclass_params['rtt_ping_duration'],
+                ping_size=self.testclass_params['ping_size'])
+
+        if testcase_params['test_type'] == 'test_ping_range':
+            start_atten = self.get_range_start_atten(testcase_params)
+            num_atten_steps = int(
+                (self.testclass_params['range_atten_stop'] - start_atten) /
+                self.testclass_params['range_atten_step'])
+            testcase_params['atten_range'] = [
+                start_atten + x * self.testclass_params['range_atten_step']
+                for x in range(0, num_atten_steps)
+            ]
+        else:
+            testcase_params['atten_range'] = self.testclass_params[
+                'rtt_test_attenuation']
+        return testcase_params
+
+    def _test_ping(self, testcase_params):
+        """ Function that gets called for each range test case
+
+        The function gets called in each range test case. It customizes the
+        range test based on the test name of the test that called it
+
+        Args:
+            testcase_params: dict containing preliminary set of parameters
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+        # Run ping test
+        self.setup_ping_test(testcase_params)
+        ping_result = self.run_ping_test(testcase_params)
+        # Postprocess results
+        self.process_ping_results(testcase_params, ping_result)
+        self.testclass_results.append(ping_result)
+        self.pass_fail_check(ping_result)
+
+    def generate_test_cases(self, ap_power, channels, modes, chain_mask,
+                            test_types):
+        test_cases = []
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+        for channel, mode, chain, test_type in itertools.product(
+                channels, modes, chain_mask, test_types):
+            if channel not in allowed_configs[mode]:
+                continue
+            testcase_name = '{}_ch{}_{}_ch{}'.format(test_type, channel, mode,
+                                                     chain)
+            testcase_params = collections.OrderedDict(test_type=test_type,
+                                                      ap_power=ap_power,
+                                                      channel=channel,
+                                                      mode=mode,
+                                                      chain_mask=chain)
+            setattr(self, testcase_name,
+                    partial(self._test_ping, testcase_params))
+            test_cases.append(testcase_name)
+        return test_cases
+
+
+class WifiPing_TwoChain_Test(WifiPingTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ap_power='standard',
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            test_types=[
+                'test_ping_range', 'test_fast_ping_rtt', 'test_slow_ping_rtt'
+            ],
+            chain_mask=['2x2'])
+
+
+class WifiPing_PerChainRange_Test(WifiPingTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ap_power='standard',
+            chain_mask=['0', '1', '2x2'],
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            test_types=['test_ping_range'])
+
+
+class WifiPing_LowPowerAP_Test(WifiPingTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ap_power='low_power',
+            chain_mask=['0', '1', '2x2'],
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            test_types=['test_ping_range'])
+
+
+# Over-the air version of ping tests
+class WifiOtaPingTest(WifiPingTest):
+    """Class to test over-the-air ping
+
+    This class tests WiFi ping performance in an OTA chamber. It enables
+    setting turntable orientation and other chamber parameters to study
+    performance in varying channel conditions
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = False
+
+    def setup_class(self):
+        WifiPingTest.setup_class(self)
+        self.ota_chamber = ota_chamber.create(
+            self.user_params['OTAChamber'])[0]
+
+    def teardown_class(self):
+        WifiPingTest.teardown_class(self)
+        self.process_testclass_results()
+        self.ota_chamber.reset_chamber()
+
+    def process_testclass_results(self):
+        """Saves all test results to enable comparison."""
+        WifiPingTest.process_testclass_results(self)
+
+        range_vs_angle = collections.OrderedDict()
+        for test in self.testclass_results:
+            curr_params = test['testcase_params']
+            curr_config = curr_params['channel']
+            if curr_config in range_vs_angle:
+                if curr_params['position'] not in range_vs_angle[curr_config][
+                        'position']:
+                    range_vs_angle[curr_config]['position'].append(
+                        curr_params['position'])
+                    range_vs_angle[curr_config]['range'].append(test['range'])
+                    range_vs_angle[curr_config]['llstats_at_range'].append(
+                        test['llstats_at_range'])
+                else:
+                    range_vs_angle[curr_config]['range'][-1] = test['range']
+                    range_vs_angle[curr_config]['llstats_at_range'][-1] = test[
+                        'llstats_at_range']
+            else:
+                range_vs_angle[curr_config] = {
+                    'position': [curr_params['position']],
+                    'range': [test['range']],
+                    'llstats_at_range': [test['llstats_at_range']]
+                }
+        chamber_mode = self.testclass_results[0]['testcase_params'][
+            'chamber_mode']
+        if chamber_mode == 'orientation':
+            x_label = 'Angle (deg)'
+        elif chamber_mode == 'stepped stirrers':
+            x_label = 'Position Index'
+        figure = wputils.BokehFigure(
+            title='Range vs. Position',
+            x_label=x_label,
+            primary_y_label='Range (dB)',
+        )
+        for channel, channel_data in range_vs_angle.items():
+            figure.add_line(x_data=channel_data['position'],
+                            y_data=channel_data['range'],
+                            hover_text=channel_data['llstats_at_range'],
+                            legend='Channel {}'.format(channel))
+            average_range = sum(channel_data['range']) / len(
+                channel_data['range'])
+            self.log.info('Average range for Channel {} is: {}dB'.format(
+                channel, average_range))
+            metric_name = 'ota_summary_ch{}.avg_range'.format(channel)
+            self.testclass_metric_logger.add_metric(metric_name, average_range)
+        current_context = context.get_current_context().get_full_output_path()
+        plot_file_path = os.path.join(current_context, 'results.html')
+        figure.generate_figure(plot_file_path)
+
+        # Save results
+        results_file_path = os.path.join(current_context,
+                                         'testclass_summary.json')
+        with open(results_file_path, 'w') as results_file:
+            json.dump(range_vs_angle, results_file, indent=4)
+
+    def setup_ping_test(self, testcase_params):
+        WifiPingTest.setup_ping_test(self, testcase_params)
+        # Setup turntable
+        if testcase_params['chamber_mode'] == 'orientation':
+            self.ota_chamber.set_orientation(testcase_params['position'])
+        elif testcase_params['chamber_mode'] == 'stepped stirrers':
+            self.ota_chamber.step_stirrers(testcase_params['total_positions'])
+
+    def extract_test_id(self, testcase_params, id_fields):
+        test_id = collections.OrderedDict(
+            (param, testcase_params[param]) for param in id_fields)
+        return test_id
+
+    def get_range_start_atten(self, testcase_params):
+        """Gets the starting attenuation for this ping test.
+
+        The function gets the starting attenuation by checking whether a test
+        at the same configuration has executed. If so it sets the starting
+        point a configurable number of dBs below the reference test.
+
+        Returns:
+            start_atten: starting attenuation for current test
+        """
+        # If the test is being retried, start from the beginning
+        if self.retry_flag:
+            self.log.info('Retry flag set. Setting attenuation to minimum.')
+            return self.testclass_params['range_atten_start']
+        # Get the current and reference test config. The reference test is the
+        # one performed at the current MCS+1
+        ref_test_params = self.extract_test_id(testcase_params,
+                                               ['channel', 'mode'])
+        # Check if reference test has been run and set attenuation accordingly
+        previous_params = [
+            self.extract_test_id(result['testcase_params'],
+                                 ['channel', 'mode'])
+            for result in self.testclass_results
+        ]
+        try:
+            ref_index = previous_params[::-1].index(ref_test_params)
+            ref_index = len(previous_params) - 1 - ref_index
+            start_atten = self.testclass_results[ref_index][
+                'atten_at_range'] - (
+                    self.testclass_params['adjacent_range_test_gap'])
+        except ValueError:
+            self.log.info(
+                'Reference test not found. Starting from {} dB'.format(
+                    self.testclass_params['range_atten_start']))
+            start_atten = self.testclass_params['range_atten_start']
+        return start_atten
+
+    def generate_test_cases(self, ap_power, channels, modes, chamber_mode,
+                            positions):
+        test_cases = []
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+        for channel, mode, position in itertools.product(
+                channels, modes, positions):
+            if channel not in allowed_configs[mode]:
+                continue
+            testcase_name = 'test_ping_range_ch{}_{}_pos{}'.format(
+                channel, mode, position)
+            testcase_params = collections.OrderedDict(
+                test_type='test_ping_range',
+                ap_power=ap_power,
+                channel=channel,
+                mode=mode,
+                chain_mask='2x2',
+                chamber_mode=chamber_mode,
+                total_positions=len(positions),
+                position=position)
+            setattr(self, testcase_name,
+                    partial(self._test_ping, testcase_params))
+            test_cases.append(testcase_name)
+        return test_cases
+
+
+class WifiOtaPing_TenDegree_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(ap_power='standard',
+                                              channels=[6, 36, 149],
+                                              modes=['VHT20'],
+                                              chamber_mode='orientation',
+                                              positions=list(range(0, 360,
+                                                                   10)))
+
+
+class WifiOtaPing_45Degree_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(
+            ap_power='standard',
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20'],
+            chamber_mode='orientation',
+            positions=list(range(0, 360, 45)))
+
+
+class WifiOtaPing_SteppedStirrers_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(ap_power='standard',
+                                              channels=[6, 36, 149],
+                                              modes=['VHT20'],
+                                              chamber_mode='stepped stirrers',
+                                              positions=list(range(100)))
+
+
+class WifiOtaPing_LowPowerAP_TenDegree_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(ap_power='low_power',
+                                              channels=[6, 36, 149],
+                                              modes=['VHT20'],
+                                              chamber_mode='orientation',
+                                              positions=list(range(0, 360,
+                                                                   10)))
+
+
+class WifiOtaPing_LowPowerAP_45Degree_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(
+            ap_power='low_power',
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20'],
+            chamber_mode='orientation',
+            positions=list(range(0, 360, 45)))
+
+
+class WifiOtaPing_LowPowerAP_SteppedStirrers_Test(WifiOtaPingTest):
+    def __init__(self, controllers):
+        WifiOtaPingTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(ap_power='low_power',
+                                              channels=[6, 36, 149],
+                                              modes=['VHT20'],
+                                              chamber_mode='stepped stirrers',
+                                              positions=list(range(100)))
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiPnoTest.py b/acts_tests/tests/google/wifi/WifiPnoTest.py
new file mode 100644
index 0000000..879ca0d
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiPnoTest.py
@@ -0,0 +1,205 @@
+#
+#   Copyright 2014 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts import base_test
+from acts.test_decorators import test_tracker_info
+import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+MAX_ATTN = 95
+
+class WifiPnoTest(WifiBaseTest):
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ["attn_vals", "pno_interval"]
+        opt_param = ["reference_networks"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                ap_count=2)
+        self.pno_network_a = self.reference_networks[0]['2g']
+        self.pno_network_b = self.reference_networks[0]['5g']
+        if "OpenWrtAP" in self.user_params:
+            self.pno_network_b = self.reference_networks[1]['5g']
+        self.attn_a = self.attenuators[0]
+        self.attn_b = self.attenuators[1]
+        # Disable second AP's networks, so that it does not interfere during PNO
+        self.attenuators[2].set_atten(MAX_ATTN)
+        self.attenuators[3].set_atten(MAX_ATTN)
+        self.set_attns("default")
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wifiStartTrackingStateChange()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+        self.dut.ed.clear_all_events()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wifiStopTrackingStateChange()
+        wutils.reset_wifi(self.dut)
+        self.dut.ed.clear_all_events()
+        self.set_attns("default")
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+
+    def set_attns(self, attn_val_name):
+        """Sets attenuation values on attenuators used in this test.
+
+        Args:
+            attn_val_name: Name of the attenuation value pair to use.
+        """
+        self.log.info("Set attenuation values to %s",
+                      self.attn_vals[attn_val_name])
+        try:
+            self.attn_a.set_atten(self.attn_vals[attn_val_name][0])
+            self.attn_b.set_atten(self.attn_vals[attn_val_name][1])
+        except:
+            self.log.error("Failed to set attenuation values %s.",
+                           attn_val_name)
+            raise
+
+    def trigger_pno_and_assert_connect(self, attn_val_name, expected_con):
+        """Sets attenuators to disconnect current connection to trigger PNO.
+        Validate that the DUT connected to the new SSID as expected after PNO.
+
+        Args:
+            attn_val_name: Name of the attenuation value pair to use.
+            expected_con: The expected info of the network to we expect the DUT
+                to roam to.
+        """
+        connection_info = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Triggering PNO connect from %s to %s",
+                      connection_info[WifiEnums.SSID_KEY],
+                      expected_con[WifiEnums.SSID_KEY])
+        self.set_attns(attn_val_name)
+        self.log.info("Wait %ss for PNO to trigger.", self.pno_interval)
+        time.sleep(self.pno_interval)
+        try:
+            self.log.info("Connected to %s network after PNO interval"
+                          % self.dut.droid.wifiGetConnectionInfo())
+            expected_ssid = expected_con[WifiEnums.SSID_KEY]
+            verify_con = {WifiEnums.SSID_KEY: expected_ssid}
+            wutils.verify_wifi_connection_info(self.dut, verify_con)
+            self.log.info("Connected to %s successfully after PNO",
+                          expected_ssid)
+        finally:
+            pass
+
+    def add_and_enable_test_networks(self, num_networks):
+        """Add some test networks to the device and enable them.
+
+        Args:
+            num_networks: Number of networks to add.
+        """
+        ssid_name_base = "pno_test_network_"
+        for i in range(0, num_networks):
+            network = {}
+            network[WifiEnums.SSID_KEY] = ssid_name_base + str(i)
+            network[WifiEnums.PWD_KEY] = "pno_test"
+            self.add_network_and_enable(network)
+
+    def add_network_and_enable(self, network):
+        """Add a network and enable it.
+
+        Args:
+            network : Network details for the network to be added.
+
+        """
+        ret = self.dut.droid.wifiAddNetwork(network)
+        asserts.assert_true(ret != -1, "Add network %r failed" % network)
+        self.dut.droid.wifiEnableNetwork(ret, 0)
+
+
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="33d3cae4-5fa7-4e90-b9e2-5d3747bba64c")
+    def test_simple_pno_connection_to_2g(self):
+        """Test PNO triggered autoconnect to a network.
+
+        Steps:
+        1. Switch off the screen on the device.
+        2. Save 2 valid network configurations (a & b) in the device.
+        3. Attenuate 5Ghz network and wait for a few seconds to trigger PNO.
+        4. Check the device connected to 2Ghz network automatically.
+        """
+        self.add_network_and_enable(self.pno_network_a)
+        self.add_network_and_enable(self.pno_network_b)
+        self.trigger_pno_and_assert_connect("a_on_b_off", self.pno_network_a)
+
+    @test_tracker_info(uuid="39b945a1-830f-4f11-9e6a-9e9641066a96")
+    def test_simple_pno_connection_to_5g(self):
+        """Test PNO triggered autoconnect to a network.
+
+        Steps:
+        1. Switch off the screen on the device.
+        2. Save 2 valid network configurations (a & b) in the device.
+        3. Attenuate 2Ghz network and wait for a few seconds to trigger PNO.
+        4. Check the device connected to 5Ghz network automatically.
+
+        """
+        self.add_network_and_enable(self.pno_network_a)
+        self.add_network_and_enable(self.pno_network_b)
+        self.trigger_pno_and_assert_connect("b_on_a_off", self.pno_network_b)
+
+    @test_tracker_info(uuid="844b15be-ff45-4b09-a11b-0b2b4bb13b22")
+    def test_pno_connection_with_multiple_saved_networks(self):
+        """Test PNO triggered autoconnect to a network when there are more
+        than 16 networks saved in the device.
+
+        16 is the max list size of PNO watch list for most devices. The device should automatically
+        pick the 16 most recently connected networks. For networks that were never connected, the
+        networks seen in the previous scan result would have higher priority.
+
+        Steps:
+        1. Save 16 test network configurations in the device.
+        2. Add 2 connectable networks and do a normal scan.
+        3. Trigger PNO scan
+        """
+        self.add_and_enable_test_networks(16)
+        self.add_network_and_enable(self.pno_network_a)
+        self.add_network_and_enable(self.pno_network_b)
+        # Force single scan so that both networks become preferred before PNO.
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        self.dut.droid.goToSleepNow()
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        time.sleep(10)
+        self.trigger_pno_and_assert_connect("b_on_a_off", self.pno_network_b)
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiPreFlightTest.py b/acts_tests/tests/google/wifi/WifiPreFlightTest.py
new file mode 100644
index 0000000..14a4190
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiPreFlightTest.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import copy
+import pprint
+import time
+
+import acts.base_test
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+SCAN_TIME = 30
+WAIT_TIME = 2
+
+
+class WifiPreFlightTest(WifiBaseTest):
+    """ Pre-flight checks for Wifi tests.
+
+    Test Bed Requirement:
+    * One Android device
+    * 4 reference networks - two 2G and two 5G networks
+    * Attenuators to attenuate each reference network
+
+    Tests:
+    * Check if reference networks show up in wifi scan
+    * Check if attenuators attenuate the correct network
+    """
+
+    def setup_class(self):
+        super().setup_class()
+        self.WIFI_2G = "2g"
+        self.WIFI_5G = "5g"
+        self.PASSWORD = "password"
+        self.MIN_SIGNAL_LEVEL = -45
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_toggle_state(self.dut, True)
+
+        # Get reference networks as a list
+        opt_params = ["reference_networks"]
+        self.unpack_userparams(opt_param_names=opt_params)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(ap_count=2)
+        networks = []
+        for ref_net in self.reference_networks:
+            networks.append(ref_net[self.WIFI_2G])
+            networks.append(ref_net[self.WIFI_5G])
+        self.reference_networks = networks
+        asserts.assert_true(
+            len(self.reference_networks) == 4,
+            "Need at least 4 reference network with psk.")
+
+    def teardown_class(self):
+        wutils.reset_wifi(self.dut)
+        for a in self.attenuators:
+            a.set_atten(0)
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """ Helper functions """
+    def _find_reference_networks_no_attn(self):
+        """ Verify that when ATTN set to 0, all reference networks
+            show up in the scanned results
+
+            Args:
+            1. List of reference networks
+
+            Returns:
+            1. List of networks not found. Empty if all reference
+               networks are found
+        """
+        found_networks = copy.deepcopy(self.target_networks)
+        start_time = time.time()
+        while(time.time() < start_time + SCAN_TIME):
+            if not found_networks:
+                break
+            time.sleep(WAIT_TIME)
+            scanned_networks = self.dut.droid.wifiGetScanResults()
+            self.log.info("SCANNED RESULTS %s" % scanned_networks)
+            for net in self.target_networks:
+                if net in found_networks:
+                    result = wutils.match_networks(net, scanned_networks)
+                    if result and result[0]['level'] > self.MIN_SIGNAL_LEVEL:
+                        found_networks.remove(net)
+                    elif result:
+                        self.log.warn("Signal strength for %s is low: %sdBm"
+                                      % (net, result[0]['level']))
+        return found_networks
+
+    def _find_network_after_setting_attn(self, target_network):
+        """ Find network after setting attenuation
+
+            Args:
+            1. target_network to find in the scanned_results
+
+            Returns:
+            1. True if
+               a. if max_attn is set and target_network not found
+            2. False if not
+        """
+        start_time = time.time()
+        while(time.time() < start_time + SCAN_TIME):
+            time.sleep(WAIT_TIME)
+            scanned_networks = self.dut.droid.wifiGetScanResults()
+            self.log.info("SCANNED RESULTS %s" % scanned_networks)
+            result = wutils.match_networks(target_network, scanned_networks)
+            if not result:
+                return True
+        return False
+
+    """ Tests """
+    def test_attenuators(self):
+        """ Test if attenuating a channel, disables the correct
+            reference network
+
+            Reference networks for each testbed should match
+            attenuators as follows
+
+            wh_ap1_2g - channel 1
+            wh_ap1_5g - channel 2
+            wh_ap2_2g - channel 3
+            wh_ap2_5g - channel 4
+
+            Steps:
+            1. Set attenuation on each channel to 95
+            2. Verify that the corresponding network does not show
+               up in the scanned results
+        """
+        # Set attenuation to 0 and verify reference
+        # networks show up in the scanned results
+        self.log.info("Verify if all reference networks show with "
+                      "attenuation set to 0")
+        if getattr(self, "attenuators", []):
+            for a in self.attenuators:
+                a.set_atten(0)
+        self.target_networks = []
+        for ref_net in self.reference_networks:
+            self.target_networks.append( {'BSSID': ref_net['bssid']} )
+        result = self._find_reference_networks_no_attn()
+        asserts.assert_true(not result,
+                            "Did not find or signal strength too low "
+                            "for the following reference networks\n%s\n" % result)
+
+        # attenuate 1 channel at a time and find the network
+        self.log.info("Verify if attenuation channel matches with "
+                      "correct reference network")
+        found_networks = []
+        for i in range(len(self.attenuators)):
+            target_network = {}
+            target_network['BSSID'] = self.reference_networks[i]['bssid']
+
+            # set the attn to max and verify target network is not found
+            self.attenuators[i].set_atten(95)
+            result = self._find_network_after_setting_attn(target_network)
+            if result:
+                target_network['ATTN'] = i
+                found_networks.append(target_network)
+
+        asserts.assert_true(not found_networks,
+                            "Attenuators did not match the networks\n %s\n"
+                            % pprint.pformat(found_networks))
diff --git a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
new file mode 100644
index 0000000..bfc96d5
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
@@ -0,0 +1,779 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import json
+import math
+import os
+import time
+from acts import asserts
+from acts import base_test
+from acts import context
+from acts import utils
+from acts.controllers import iperf_server as ipf
+from acts.controllers.utils_lib import ssh
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+
+SHORT_SLEEP = 1
+MED_SLEEP = 5
+TRAFFIC_GAP_THRESH = 0.5
+IPERF_INTERVAL = 0.25
+
+
+class WifiRoamingPerformanceTest(base_test.BaseTestClass):
+    """Class for ping-based Wifi performance tests.
+
+    This class implements WiFi ping performance tests such as range and RTT.
+    The class setups up the AP in the desired configurations, configures
+    and connects the phone to the AP, and runs  For an example config file to
+    run this test class see example_connectivity_performance_ap_sta.json.
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardwares and compiles parameters that are
+        common to all tests in this class.
+        """
+        self.dut = self.android_devices[-1]
+        req_params = [
+            'RetailAccessPoints', 'roaming_test_params', 'testbed_params'
+        ]
+        opt_params = ['main_network', 'RemoteServer']
+        self.unpack_userparams(req_params, opt_params)
+        self.testclass_params = self.roaming_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.remote_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.remote_server.setup_master_ssh()
+        self.iperf_server = self.iperf_servers[0]
+        self.iperf_client = self.iperf_clients[0]
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+        # Get RF connection map
+        self.log.info("Getting RF connection map.")
+        wutils.wifi_toggle_state(self.dut, True)
+        self.rf_map_by_network, self.rf_map_by_atten = (
+            wputils.get_full_rf_connection_map(self.attenuators, self.dut,
+                                               self.remote_server,
+                                               self.main_network))
+        self.log.info("RF Map (by Network): {}".format(self.rf_map_by_network))
+        self.log.info("RF Map (by Atten): {}".format(self.rf_map_by_atten))
+
+        #Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                                "Can not turn on airplane mode.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+    def pass_fail_traffic_continuity(self, result):
+        """Pass fail check for traffic continuity
+
+        Currently, the function only reports test results and implicitly passes
+        the test. A pass fail criterion is current being researched.
+
+        Args:
+            result: dict containing test results
+        """
+        self.log.info('Detected {} roam transitions:'.format(
+            len(result['roam_transitions'])))
+        for event in result['roam_transitions']:
+            self.log.info('Roam: {} -> {})'.format(event[0], event[1]))
+        self.log.info('Roam transition statistics: {}'.format(
+            result['roam_counts']))
+
+        formatted_traffic_gaps = [
+            round(gap, 2) for gap in result['traffic_disruption']
+        ]
+        self.log.info('Detected {} traffic gaps of duration: {}'.format(
+            len(result['traffic_disruption']), formatted_traffic_gaps))
+
+        if result['total_roams'] > 0:
+            disruption_percentage = (len(result['traffic_disruption']) /
+                                     result['total_roams']) * 100
+            max_disruption = max(result['traffic_disruption'])
+        else:
+            disruption_percentage = 0
+            max_disruption = 0
+        self.testcase_metric_logger.add_metric('disruption_percentage',
+                                               disruption_percentage)
+        self.testcase_metric_logger.add_metric('max_disruption',
+                                               max_disruption)
+
+        if disruption_percentage == 0:
+            asserts.explicit_pass('Test passed. No traffic disruptions found.')
+        elif max_disruption > self.testclass_params[
+                'traffic_disruption_threshold']:
+            asserts.fail('Test failed. Disruption Percentage = {}%. '
+                         'Max traffic disruption: {}s.'.format(
+                             disruption_percentage, max_disruption))
+        else:
+            asserts.explicit_pass('Test failed. Disruption Percentage = {}%. '
+                                  'Max traffic disruption: {}s.'.format(
+                                      disruption_percentage, max_disruption))
+
+    def pass_fail_roaming_consistency(self, results_dict):
+        """Function to evaluate roaming consistency results.
+
+        The function looks for the roams recorded in multiple runs of the same
+        attenuation waveform and checks that the DUT reliably roams to the
+        same network
+
+        Args:
+            results_dict: dict containing consistency test results
+        """
+        test_fail = False
+        for secondary_atten, roam_stats in results_dict['roam_stats'].items():
+            total_roams = sum(list(roam_stats.values()))
+            common_roam = max(roam_stats.keys(), key=(lambda k: roam_stats[k]))
+            common_roam_frequency = roam_stats[common_roam] / total_roams
+            self.log.info(
+                '{}dB secondary atten. Most common roam: {}. Frequency: {}'.
+                format(secondary_atten, common_roam, common_roam_frequency))
+            if common_roam_frequency < self.testclass_params[
+                    'consistency_threshold']:
+                test_fail = True
+                self.log.info('Unstable Roams at {}dB secondary att'.format(
+                    secondary_atten))
+        self.testcase_metric_logger.add_metric('common_roam_frequency',
+                                               common_roam_frequency)
+        if test_fail:
+            asserts.fail('Incosistent roaming detected.')
+        else:
+            asserts.explicit_pass('Consistent roaming at all levels.')
+
+    def process_traffic_continuity_results(self, testcase_params, result):
+        """Function to process traffic results.
+
+        The function looks for traffic gaps during a roaming test
+
+        Args:
+            testcase_params: dict containing all test results and meta data
+            results_dict: dict containing consistency test results
+        """
+        self.detect_roam_events(result)
+        current_context = context.get_current_context().get_full_output_path()
+        plot_file_path = os.path.join(current_context,
+                                      self.current_test_name + '.html')
+
+        if 'ping' in self.current_test_name:
+            self.detect_ping_gaps(result)
+            self.plot_ping_result(testcase_params,
+                                  result,
+                                  output_file_path=plot_file_path)
+        elif 'iperf' in self.current_test_name:
+            self.detect_iperf_gaps(result)
+            self.plot_iperf_result(testcase_params,
+                                   result,
+                                   output_file_path=plot_file_path)
+
+        results_file_path = os.path.join(current_context,
+                                         self.current_test_name + '.json')
+        with open(results_file_path, 'w') as results_file:
+            json.dump(wputils.serialize_dict(result), results_file, indent=4)
+
+    def process_consistency_results(self, testcase_params, results_dict):
+        """Function to process roaming consistency results.
+
+        The function looks compiles the test of roams recorded in consistency
+        tests and plots results for easy visualization.
+
+        Args:
+            testcase_params: dict containing all test results and meta data
+            results_dict: dict containing consistency test results
+        """
+        # make figure placeholder and get relevant functions
+        if 'ping' in self.current_test_name:
+            detect_gaps = self.detect_ping_gaps
+            plot_result = self.plot_ping_result
+            primary_y_axis = 'RTT (ms)'
+        elif 'iperf' in self.current_test_name:
+            detect_gaps = self.detect_iperf_gaps
+            plot_result = self.plot_iperf_result
+            primary_y_axis = 'Throughput (Mbps)'
+        # loop over results
+        roam_stats = collections.OrderedDict()
+        current_context = context.get_current_context().get_full_output_path()
+        for secondary_atten, results_list in results_dict.items():
+            figure = wputils.BokehFigure(title=self.current_test_name,
+                                         x_label='Time (ms)',
+                                         primary_y_label=primary_y_axis,
+                                         secondary_y_label='RSSI (dBm)')
+            roam_stats[secondary_atten] = collections.OrderedDict()
+            for result in results_list:
+                self.detect_roam_events(result)
+                for roam_transition, count in result['roam_counts'].items():
+                    roam_stats[secondary_atten][
+                        roam_transition] = roam_stats[secondary_atten].get(
+                            roam_transition, 0) + count
+                detect_gaps(result)
+                plot_result(testcase_params, result, figure=figure)
+            # save plot
+            plot_file_name = (self.current_test_name + '_' +
+                              str(secondary_atten) + '.html')
+
+            plot_file_path = os.path.join(current_context, plot_file_name)
+            figure.save_figure(plot_file_path)
+        results_dict['roam_stats'] = roam_stats
+
+        results_file_path = os.path.join(current_context,
+                                         self.current_test_name + '.json')
+        with open(results_file_path, 'w') as results_file:
+            json.dump(wputils.serialize_dict(result), results_file, indent=4)
+
+    def detect_roam_events(self, result):
+        """Function to process roaming results.
+
+        The function detects roams by looking at changes in BSSID and compiles
+        meta data about each roam, e.g., RSSI before and after a roam. The
+        function then calls the relevant method to process traffic results and
+        report traffic disruptions.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+            result: dict containing test results
+        """
+        roam_events = [
+            (idx, idx + 1)
+            for idx in range(len(result['rssi_result']['bssid']) - 1)
+            if result['rssi_result']['bssid'][idx] != result['rssi_result']
+            ['bssid'][idx + 1]
+        ]
+
+        def ignore_entry(vals):
+            for val in vals:
+                if val in {0} or math.isnan(val):
+                    return True
+            return False
+
+        for roam_idx, roam_event in enumerate(roam_events):
+            # Find true roam start by scanning earlier samples for valid data
+            while ignore_entry([
+                    result['rssi_result']['frequency'][roam_event[0]],
+                    result['rssi_result']['signal_poll_rssi']['data'][
+                        roam_event[0]]
+            ]):
+                roam_event = (roam_event[0] - 1, roam_event[1])
+                roam_events[roam_idx] = roam_event
+            # Find true roam end by scanning later samples for valid data
+            while ignore_entry([
+                    result['rssi_result']['frequency'][roam_event[1]],
+                    result['rssi_result']['signal_poll_rssi']['data'][
+                        roam_event[1]]
+            ]):
+                roam_event = (roam_event[0], roam_event[1] + 1)
+                roam_events[roam_idx] = roam_event
+
+        roam_events = list(set(roam_events))
+        roam_events.sort(key=lambda event_tuple: event_tuple[1])
+        roam_transitions = []
+        roam_counts = {}
+        total_roams = 0
+        for event in roam_events:
+            from_bssid = next(
+                key for key, value in self.main_network.items()
+                if value['BSSID'] == result['rssi_result']['bssid'][event[0]])
+            to_bssid = next(
+                key for key, value in self.main_network.items()
+                if value['BSSID'] == result['rssi_result']['bssid'][event[1]])
+            curr_bssid_transition = (from_bssid, to_bssid)
+            curr_roam_transition = (
+                (from_bssid,
+                 result['rssi_result']['signal_poll_rssi']['data'][event[0]]),
+                (to_bssid,
+                 result['rssi_result']['signal_poll_rssi']['data'][event[1]]))
+            roam_transitions.append(curr_roam_transition)
+            roam_counts[curr_bssid_transition] = roam_counts.get(
+                curr_bssid_transition, 0) + 1
+            total_roams = total_roams + 1
+        result['roam_events'] = roam_events
+        result['roam_transitions'] = roam_transitions
+        result['roam_counts'] = roam_counts
+        result['total_roams'] = total_roams
+
+    def detect_ping_gaps(self, result):
+        """Function to process ping results.
+
+        The function looks for gaps in iperf traffic and reports them as
+        disruptions due to roams.
+
+        Args:
+            result: dict containing test results
+        """
+        traffic_disruption = [
+            x for x in result['ping_result']['ping_interarrivals']
+            if x > TRAFFIC_GAP_THRESH
+        ]
+        result['traffic_disruption'] = traffic_disruption
+
+    def detect_iperf_gaps(self, result):
+        """Function to process iperf results.
+
+        The function looks for gaps in iperf traffic and reports them as
+        disruptions due to roams.
+
+        Args:
+            result: dict containing test results
+        """
+        tput_thresholding = [tput < 1 for tput in result['throughput']]
+        window_size = int(TRAFFIC_GAP_THRESH / IPERF_INTERVAL)
+        tput_thresholding = [
+            any(tput_thresholding[max(0, idx - window_size):idx])
+            for idx in range(1,
+                             len(tput_thresholding) + 1)
+        ]
+
+        traffic_disruption = []
+        current_disruption = 1 - window_size
+        for tput_low in tput_thresholding:
+            if tput_low:
+                current_disruption += 1
+            elif current_disruption > window_size:
+                traffic_disruption.append(current_disruption * IPERF_INTERVAL)
+                current_disruption = 1 - window_size
+            else:
+                current_disruption = 1 - window_size
+        result['traffic_disruption'] = traffic_disruption
+
+    def plot_ping_result(self,
+                         testcase_params,
+                         result,
+                         figure=None,
+                         output_file_path=None):
+        """Function to plot ping results.
+
+        The function plots ping RTTs along with RSSI over time during a roaming
+        test.
+
+        Args:
+            testcase_params: dict containing all test params
+            result: dict containing test results
+            figure: optional bokeh figure object to add current plot to
+            output_file_path: optional path to output file
+        """
+        if not figure:
+            figure = wputils.BokehFigure(title=self.current_test_name,
+                                         x_label='Time (ms)',
+                                         primary_y_label='RTT (ms)',
+                                         secondary_y_label='RSSI (dBm)')
+        figure.add_line(x_data=result['ping_result']['time_stamp'],
+                        y_data=result['ping_result']['rtt'],
+                        legend='Ping RTT',
+                        width=1)
+        figure.add_line(
+            x_data=result['rssi_result']['time_stamp'],
+            y_data=result['rssi_result']['signal_poll_rssi']['data'],
+            legend='RSSI',
+            y_axis='secondary')
+        figure.generate_figure(output_file_path)
+
+    def plot_iperf_result(self,
+                          testcase_params,
+                          result,
+                          figure=None,
+                          output_file_path=None):
+        """Function to plot iperf results.
+
+        The function plots iperf throughput and RSSI over time during a roaming
+        test.
+
+        Args:
+            testcase_params: dict containing all test params
+            result: dict containing test results
+            figure: optional bokeh figure object to add current plot to
+            output_file_path: optional path to output file
+        """
+        if not figure:
+            figure = wputils.BokehFigure(title=self.current_test_name,
+                                         x_label='Time (s)',
+                                         primary_y_label='Throughput (Mbps)',
+                                         secondary_y_label='RSSI (dBm)')
+        iperf_time_stamps = [
+            idx * IPERF_INTERVAL for idx in range(len(result['throughput']))
+        ]
+        figure.add_line(iperf_time_stamps,
+                        result['throughput'],
+                        'Throughput',
+                        width=1)
+        figure.add_line(result['rssi_result']['time_stamp'],
+                        result['rssi_result']['signal_poll_rssi']['data'],
+                        'RSSI',
+                        y_axis='secondary')
+
+        figure.generate_figure(output_file_path)
+
+    def setup_ap(self, testcase_params):
+        """Sets up the AP and attenuator to the test configuration.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        (primary_net_id,
+         primary_net_config) = next(net for net in self.main_network.items()
+                                    if net[1]['roaming_label'] == 'primary')
+        for idx, atten in enumerate(self.attenuators):
+            nets_on_port = [
+                item["network"] for item in self.rf_map_by_atten[idx]
+            ]
+            if primary_net_id in nets_on_port:
+                atten.set_atten(0)
+            else:
+                atten.set_atten(atten.instrument.max_atten)
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('Battery level too low. Skipping test.')
+        wutils.reset_wifi(self.dut)
+        wutils.set_wifi_country_code(self.dut,
+                                     self.testclass_params['country_code'])
+        (primary_net_id,
+         primary_net_config) = next(net for net in self.main_network.items()
+                                    if net[1]['roaming_label'] == 'primary')
+        network = primary_net_config.copy()
+        network.pop('BSSID', None)
+        self.dut.droid.wifiSetEnableAutoJoinWhenAssociated(1)
+        wutils.wifi_connect(self.dut,
+                            network,
+                            num_of_tries=5,
+                            check_connectivity=False)
+        self.dut.droid.wifiSetEnableAutoJoinWhenAssociated(1)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        if testcase_params['screen_on']:
+            self.dut.wakeup_screen()
+            self.dut.droid.wakeLockAcquireBright()
+        time.sleep(MED_SLEEP)
+
+    def setup_roaming_test(self, testcase_params):
+        """Function to set up roaming test."""
+        self.setup_ap(testcase_params)
+        self.setup_dut(testcase_params)
+
+    def run_ping_test(self, testcase_params):
+        """Main function for ping roaming tests.
+
+        Args:
+            testcase_params: dict including all test params encoded in test
+            name
+        Returns:
+            dict containing all test results and meta data
+        """
+        self.log.info('Starting ping test.')
+        ping_future = wputils.get_ping_stats_nb(
+            self.remote_server, self.dut_ip,
+            testcase_params['atten_waveforms']['length'],
+            testcase_params['ping_interval'], 64)
+        rssi_future = wputils.get_connected_rssi_nb(
+            self.dut,
+            int(testcase_params['atten_waveforms']['length'] /
+                testcase_params['rssi_polling_frequency']),
+            testcase_params['rssi_polling_frequency'])
+        self.run_attenuation_waveform(testcase_params)
+        return {
+            'ping_result': ping_future.result().as_dict(),
+            'rssi_result': rssi_future.result(),
+            'ap_settings': self.access_point.ap_settings,
+        }
+
+    def run_iperf_test(self, testcase_params):
+        """Main function for iperf roaming tests.
+
+        Args:
+            testcase_params: dict including all test params encoded in test
+            name
+        Returns:
+            result: dict containing all test results and meta data
+        """
+        self.log.info('Starting iperf test.')
+        self.iperf_server.start(extra_args='-i {}'.format(IPERF_INTERVAL))
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            iperf_server_address = self.dut_ip
+        else:
+            iperf_server_address = wputils.get_server_address(
+                self.remote_server, self.dut_ip, '255.255.255.0')
+        iperf_args = '-i {} -t {} -J'.format(
+            IPERF_INTERVAL, testcase_params['atten_waveforms']['length'])
+        if not isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            iperf_args = iperf_args + ' -R'
+        iperf_future = wputils.start_iperf_client_nb(
+            self.iperf_client, iperf_server_address, iperf_args, 0,
+            testcase_params['atten_waveforms']['length'] + MED_SLEEP)
+        rssi_future = wputils.get_connected_rssi_nb(
+            self.dut,
+            int(testcase_params['atten_waveforms']['length'] /
+                testcase_params['rssi_polling_frequency']),
+            testcase_params['rssi_polling_frequency'])
+        self.run_attenuation_waveform(testcase_params)
+        client_output_path = iperf_future.result()
+        server_output_path = self.iperf_server.stop()
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            iperf_file = server_output_path
+        else:
+            iperf_file = client_output_path
+        iperf_result = ipf.IPerfResult(iperf_file)
+        instantaneous_rates = [
+            rate * 8 * (1.024**2) for rate in iperf_result.instantaneous_rates
+        ]
+        return {
+            'throughput': instantaneous_rates,
+            'rssi_result': rssi_future.result(),
+            'ap_settings': self.access_point.ap_settings,
+        }
+
+    def run_attenuation_waveform(self, testcase_params, step_duration=1):
+        """Function that generates test params based on the test name.
+
+        Args:
+            testcase_params: dict including all test params encoded in test
+            name
+            step_duration: int representing number of seconds to dwell on each
+            atten level
+        """
+        atten_waveforms = testcase_params['atten_waveforms']
+        for atten_idx in range(atten_waveforms['length']):
+            start_time = time.time()
+            for network, atten_waveform in atten_waveforms.items():
+                for idx, atten in enumerate(self.attenuators):
+                    nets_on_port = [
+                        item["network"] for item in self.rf_map_by_atten[idx]
+                    ]
+                    if network in nets_on_port:
+                        atten.set_atten(atten_waveform[atten_idx])
+            measure_time = time.time() - start_time
+            time.sleep(step_duration - measure_time)
+
+    def compile_atten_waveforms(self, waveform_params):
+        """Function to compile all attenuation waveforms for roaming test.
+
+        Args:
+            waveform_params: list of dicts representing waveforms to generate
+        """
+        atten_waveforms = {}
+        for network in list(waveform_params[0]):
+            atten_waveforms[network] = []
+
+        for waveform in waveform_params:
+            for network, network_waveform in waveform.items():
+                waveform_vector = self.gen_single_atten_waveform(
+                    network_waveform)
+                atten_waveforms[network] += waveform_vector
+
+        waveform_lengths = {
+            len(atten_waveforms[network])
+            for network in atten_waveforms.keys()
+        }
+        if len(waveform_lengths) != 1:
+            raise ValueError(
+                'Attenuation waveform length should be equal for all networks.'
+            )
+        else:
+            atten_waveforms['length'] = waveform_lengths.pop()
+        return atten_waveforms
+
+    def gen_single_atten_waveform(self, waveform_params):
+        """Function to generate a single attenuation waveform for roaming test.
+
+        Args:
+            waveform_params: dict representing waveform to generate
+        """
+        waveform_vector = []
+        for section in range(len(waveform_params['atten_levels']) - 1):
+            section_limits = waveform_params['atten_levels'][section:section +
+                                                             2]
+            up_down = (1 - 2 * (section_limits[1] < section_limits[0]))
+            temp_section = list(
+                range(section_limits[0], section_limits[1] + up_down,
+                      up_down * waveform_params['step_size']))
+            temp_section = [
+                val for val in temp_section
+                for _ in range(waveform_params['step_duration'])
+            ]
+            waveform_vector += temp_section
+        waveform_vector *= waveform_params['repetitions']
+        return waveform_vector
+
+    def parse_test_params(self, testcase_params):
+        """Function that generates test params based on the test name.
+
+        Args:
+            test_name: current test name
+        Returns:
+            testcase_params: dict including all test params encoded in test
+            name
+        """
+        if testcase_params["waveform_type"] == 'smooth':
+            testcase_params[
+                'roaming_waveforms_params'] = self.testclass_params[
+                    'smooth_roaming_waveforms']
+        elif testcase_params["waveform_type"] == 'failover':
+            testcase_params[
+                'roaming_waveforms_params'] = self.testclass_params[
+                    'failover_roaming_waveforms']
+        elif testcase_params["waveform_type"] == 'consistency':
+            testcase_params[
+                'roaming_waveforms_params'] = self.testclass_params[
+                    'consistency_waveforms']
+        return testcase_params
+
+    def _test_traffic_continuity(self, testcase_params):
+        """Test function for traffic continuity"""
+        # Compile test parameters from config and test name
+        testcase_params = self.parse_test_params(testcase_params)
+        testcase_params.update(self.testclass_params)
+        testcase_params['atten_waveforms'] = self.compile_atten_waveforms(
+            testcase_params['roaming_waveforms_params'])
+        # Run traffic test
+        self.setup_roaming_test(testcase_params)
+        if testcase_params['traffic_type'] == 'iperf':
+            result = self.run_iperf_test(testcase_params)
+        elif testcase_params['traffic_type'] == 'ping':
+            result = self.run_ping_test(testcase_params)
+        # Postprocess results
+        self.process_traffic_continuity_results(testcase_params, result)
+        self.pass_fail_traffic_continuity(result)
+
+    def _test_roam_consistency(self, testcase_params):
+        """Test function for roaming consistency"""
+        testcase_params = self.parse_test_params(testcase_params)
+        testcase_params.update(self.testclass_params)
+        # Run traffic test
+        secondary_attens = range(
+            self.testclass_params['consistency_waveforms']['secondary_loop']
+            ['atten_levels'][0], self.testclass_params['consistency_waveforms']
+            ['secondary_loop']['atten_levels'][1],
+            self.testclass_params['consistency_waveforms']['secondary_loop']
+            ['step_size'])
+        results = collections.OrderedDict()
+        for secondary_atten in secondary_attens:
+            primary_waveform = self.gen_single_atten_waveform(
+                testcase_params['roaming_waveforms_params']['primary_sweep'])
+            secondary_waveform_params = {
+                'atten_levels': [secondary_atten, secondary_atten],
+                'step_size': 1,
+                'step_duration': len(primary_waveform),
+                'repetitions': 1
+            }
+            secondary_waveform = self.gen_single_atten_waveform(
+                secondary_waveform_params)
+            testcase_params['atten_waveforms'] = {
+                'length': len(primary_waveform)
+            }
+            for network_key, network_info in self.main_network.items():
+                if 'primary' in network_info['roaming_label']:
+                    testcase_params['atten_waveforms'][
+                        network_key] = primary_waveform
+                else:
+                    testcase_params['atten_waveforms'][
+                        network_key] = secondary_waveform
+            results[secondary_atten] = []
+            for run in range(self.testclass_params['consistency_num_runs']):
+                self.setup_roaming_test(testcase_params)
+                results[secondary_atten].append(
+                    self.run_ping_test(testcase_params))
+        # Postprocess results
+        self.process_consistency_results(testcase_params, results)
+        self.pass_fail_roaming_consistency(results)
+
+    def test_consistency_roaming_screen_on_ping(self):
+        testcase_params = {
+            "waveform_type": "consistency",
+            "screen_on": 1,
+            "traffic_type": "ping"
+        }
+        self._test_roam_consistency(testcase_params)
+
+    def test_smooth_roaming_screen_on_ping_continuity(self):
+        testcase_params = {
+            "waveform_type": "smooth",
+            "screen_on": 1,
+            "traffic_type": "ping"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_smooth_roaming_screen_on_iperf_continuity(self):
+        testcase_params = {
+            "waveform_type": "smooth",
+            "screen_on": 1,
+            "traffic_type": "iperf"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_failover_roaming_screen_on_ping_continuity(self):
+        testcase_params = {
+            "waveform_type": "failover",
+            "screen_on": 1,
+            "traffic_type": "ping"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_failover_roaming_screen_on_iperf_continuity(self):
+        testcase_params = {
+            "waveform_type": "failover",
+            "screen_on": 1,
+            "traffic_type": "iperf"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_smooth_roaming_screen_off_ping_continuity(self):
+        testcase_params = {
+            "waveform_type": "smooth",
+            "screen_on": 0,
+            "traffic_type": "ping"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_smooth_roaming_screen_off_iperf_continuity(self):
+        testcase_params = {
+            "waveform_type": "smooth",
+            "screen_on": 0,
+            "traffic_type": "iperf"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_failover_roaming_screen_off_ping_continuity(self):
+        testcase_params = {
+            "waveform_type": "failover",
+            "screen_on": 0,
+            "traffic_type": "ping"
+        }
+        self._test_traffic_continuity(testcase_params)
+
+    def test_failover_roaming_screen_off_iperf_continuity(self):
+        testcase_params = {
+            "waveform_type": "failover",
+            "screen_on": 0,
+            "traffic_type": "iperf"
+        }
+        self._test_traffic_continuity(testcase_params)
diff --git a/acts_tests/tests/google/wifi/WifiRoamingTest.py b/acts_tests/tests/google/wifi/WifiRoamingTest.py
new file mode 100644
index 0000000..0ef6bb6
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiRoamingTest.py
@@ -0,0 +1,184 @@
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class WifiRoamingTest(WifiBaseTest):
+
+    def setup_class(self):
+        """Configure the required networks for testing roaming."""
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ["roaming_attn",]
+        self.unpack_userparams(req_param_names=req_params,)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(ap_count=2)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                owe_network=True,
+                                                sae_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+
+    def teardown_class(self):
+        self.dut.ed.clear_all_events()
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.ed.clear_all_events()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+        wutils.set_attns(self.attenuators, "default")
+
+    ### Helper Methods ###
+
+    def roaming_from_AP1_and_AP2(self, AP1_network, AP2_network):
+        """Test roaming between two APs.
+
+        Args:
+            AP1_network: AP-1's network information.
+            AP2_network: AP-2's network information.
+
+        Steps:
+        1. Make AP1 visible, AP2 not visible.
+        2. Connect to AP1's ssid.
+        3. Make AP1 not visible, AP2 visible.
+        4. Expect DUT to roam to AP2.
+        5. Validate connection information and ping.
+        """
+        wutils.set_attns(self.attenuators, "AP1_on_AP2_off", self.roaming_attn)
+        wifi_config = AP1_network.copy()
+        wifi_config.pop("bssid")
+        wutils.connect_to_wifi_network(self.dut, wifi_config)
+        self.log.info("Roaming from %s to %s", AP1_network, AP2_network)
+        wutils.trigger_roaming_and_validate(
+            self.dut, self.attenuators, "AP1_off_AP2_on", AP2_network,
+            self.roaming_attn)
+
+    ### Test Cases ###
+
+    @test_tracker_info(uuid="db8a46f9-713f-4b98-8d9f-d36319905b0a")
+    def test_roaming_between_AP1_to_AP2_open_2g(self):
+        ap1_network = self.open_network[0]["2g"]
+        ap2_network = self.open_network[1]["2g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["2g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["2g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="0db67d9b-6ea9-4f40-acf2-155c4ecf9dc5")
+    def test_roaming_between_AP1_to_AP2_open_5g(self):
+        ap1_network = self.open_network[0]["5g"]
+        ap2_network = self.open_network[1]["5g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["5g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["5g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="eabc7319-d962-4bef-b679-725e9ff00420")
+    def test_roaming_between_AP1_to_AP2_psk_2g(self):
+        ap1_network = self.reference_networks[0]["2g"]
+        ap2_network = self.reference_networks[1]["2g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["2g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["2g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="1cf9c681-4ff0-45c1-9719-f01629f6a7f7")
+    def test_roaming_between_AP1_to_AP2_psk_5g(self):
+        ap1_network = self.reference_networks[0]["5g"]
+        ap2_network = self.reference_networks[1]["5g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["5g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["5g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="a28f7d2e-fae4-4e66-b633-7ee59f8b46e0")
+    def test_roaming_between_AP1_to_AP2_owe_2g(self):
+        ap1_network = self.owe_networks[0]["2g"]
+        ap2_network = self.owe_networks[1]["2g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["2g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["2g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="3c39110a-9336-4abd-b885-acbba85dc10d")
+    def test_roaming_between_AP1_to_AP2_owe_5g(self):
+        ap1_network = self.owe_networks[0]["5g"]
+        ap2_network = self.owe_networks[1]["5g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["5g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["5g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="68b2baf6-162a-44f2-a00d-4973e5ac9471")
+    def test_roaming_between_AP1_to_AP2_sae_2g(self):
+        ap1_network = self.sae_networks[0]["2g"]
+        ap2_network = self.sae_networks[1]["2g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["2g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["2g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="20e24ed3-0cd1-46dd-bd26-2183ffb443e6")
+    def test_roaming_between_AP1_to_AP2_sae_5g(self):
+        ap1_network = self.sae_networks[0]["5g"]
+        ap2_network = self.sae_networks[1]["5g"]
+        if "OpenWrtAP" in self.user_params:
+            ap1_network["bssid"] = self.bssid_map[0]["5g"][ap1_network["SSID"]]
+            ap2_network["bssid"] = self.bssid_map[1]["5g"][ap2_network["SSID"]]
+        self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+
+    @test_tracker_info(uuid="3114d625-5cdd-4205-bb46-5a9d057dc80d")
+    def test_roaming_fail_psk_2g(self):
+        network = {'SSID':'test_roaming_fail', 'password':'roam123456@'}
+        # AP2 network with incorrect password.
+        network_fail = {'SSID':'test_roaming_fail', 'password':'roam123456@#$%^'}
+        # Setup AP1 with the correct password.
+        wutils.ap_setup(self, 0, self.access_points[0], network)
+        network_bssid = self.access_points[0].get_bssid_from_ssid(
+                network["SSID"], '2g')
+        # Setup AP2 with the incorrect password.
+        wutils.ap_setup(self, 1, self.access_points[1], network_fail)
+        network_fail_bssid = self.access_points[1].get_bssid_from_ssid(
+                network_fail["SSID"], '2g')
+        network['bssid'] = network_bssid
+        network_fail['bssid'] = network_fail_bssid
+        try:
+            # Initiate roaming with AP2 configured with incorrect password.
+            self.roaming_from_AP1_and_AP2(network, network_fail)
+        except:
+            self.log.info("Roaming failed to AP2 with incorrect password.")
+            # Re-configure AP2 after roaming failed, with correct password.
+            self.log.info("Re-configuring AP2 with correct password.")
+            wutils.ap_setup(self, 1, self.access_points[1], network)
+        self.roaming_from_AP1_and_AP2(network, network_fail)
diff --git a/acts_tests/tests/google/wifi/WifiRssiTest.py b/acts_tests/tests/google/wifi/WifiRssiTest.py
new file mode 100644
index 0000000..9ff9afa
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiRssiTest.py
@@ -0,0 +1,1074 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import itertools
+import json
+import logging
+import math
+import numpy
+import os
+import statistics
+from acts import asserts
+from acts import base_test
+from acts import context
+from acts import utils
+from acts.controllers.utils_lib import ssh
+from acts.controllers import iperf_server as ipf
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from concurrent.futures import ThreadPoolExecutor
+from functools import partial
+
+SHORT_SLEEP = 1
+MED_SLEEP = 6
+CONST_3dB = 3.01029995664
+RSSI_ERROR_VAL = float('nan')
+
+
+class WifiRssiTest(base_test.BaseTestClass):
+    """Class to test WiFi RSSI reporting.
+
+    This class tests RSSI reporting on android devices. The class tests RSSI
+    accuracy by checking RSSI over a large attenuation range, checks for RSSI
+    stability over time when attenuation is fixed, and checks that RSSI quickly
+    and reacts to changes attenuation by checking RSSI trajectories over
+    configurable attenuation waveforms.For an example config file to run this
+    test class see example_connectivity_performance_ap_sta.json.
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_test_metrics = True
+
+    def setup_class(self):
+        self.dut = self.android_devices[0]
+        req_params = [
+            'RemoteServer', 'RetailAccessPoints', 'rssi_test_params',
+            'main_network', 'testbed_params'
+        ]
+        self.unpack_userparams(req_params)
+        self.testclass_params = self.rssi_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.iperf_server = self.iperf_servers[0]
+        self.iperf_client = self.iperf_clients[0]
+        self.remote_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                                "Can not turn on airplane mode.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+    def teardown_test(self):
+        self.iperf_server.stop()
+
+    def pass_fail_check_rssi_stability(self, testcase_params,
+                                       postprocessed_results):
+        """Check the test result and decide if it passed or failed.
+
+        Checks the RSSI test result and fails the test if the standard
+        deviation of signal_poll_rssi is beyond the threshold defined in the
+        config file.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+            postprocessed_results: compiled arrays of RSSI measurements
+        """
+        # Set Blackbox metric values
+        if self.publish_test_metrics:
+            self.testcase_metric_logger.add_metric(
+                'signal_poll_rssi_stdev',
+                max(postprocessed_results['signal_poll_rssi']['stdev']))
+            self.testcase_metric_logger.add_metric(
+                'chain_0_rssi_stdev',
+                max(postprocessed_results['chain_0_rssi']['stdev']))
+            self.testcase_metric_logger.add_metric(
+                'chain_1_rssi_stdev',
+                max(postprocessed_results['chain_1_rssi']['stdev']))
+
+        # Evaluate test pass/fail
+        test_failed = any([
+            stdev > self.testclass_params['stdev_tolerance']
+            for stdev in postprocessed_results['signal_poll_rssi']['stdev']
+        ])
+        test_message = (
+            'RSSI stability {0}. Standard deviation was {1} dB '
+            '(limit {2}), per chain standard deviation [{3}, {4}] dB'.format(
+                'failed' * test_failed + 'passed' * (not test_failed), [
+                    float('{:.2f}'.format(x))
+                    for x in postprocessed_results['signal_poll_rssi']['stdev']
+                ], self.testclass_params['stdev_tolerance'], [
+                    float('{:.2f}'.format(x))
+                    for x in postprocessed_results['chain_0_rssi']['stdev']
+                ], [
+                    float('{:.2f}'.format(x))
+                    for x in postprocessed_results['chain_1_rssi']['stdev']
+                ]))
+        if test_failed:
+            asserts.fail(test_message)
+        asserts.explicit_pass(test_message)
+
+    def pass_fail_check_rssi_accuracy(self, testcase_params,
+                                      postprocessed_results):
+        """Check the test result and decide if it passed or failed.
+
+        Checks the RSSI test result and compares and compute its deviation from
+        the predicted RSSI. This computation is done for all reported RSSI
+        values. The test fails if any of the RSSI values specified in
+        rssi_under_test have an average error beyond what is specified in the
+        configuration file.
+
+        Args:
+            postprocessed_results: compiled arrays of RSSI measurements
+            testcase_params: dict containing params such as list of RSSIs under
+            test, i.e., can cause test to fail and boolean indicating whether
+            to look at absolute RSSI accuracy, or centered RSSI accuracy.
+            Centered accuracy is computed after systematic RSSI shifts are
+            removed.
+        """
+        test_failed = False
+        test_message = ''
+        if testcase_params['absolute_accuracy']:
+            error_type = 'absolute'
+        else:
+            error_type = 'centered'
+
+        for key, val in postprocessed_results.items():
+            # Compute the error metrics ignoring invalid RSSI readings
+            # If all readings invalid, set error to RSSI_ERROR_VAL
+            if 'rssi' in key and 'predicted' not in key:
+                filtered_error = [x for x in val['error'] if not math.isnan(x)]
+                if filtered_error:
+                    avg_shift = statistics.mean(filtered_error)
+                    if testcase_params['absolute_accuracy']:
+                        avg_error = statistics.mean(
+                            [abs(x) for x in filtered_error])
+                    else:
+                        avg_error = statistics.mean(
+                            [abs(x - avg_shift) for x in filtered_error])
+                else:
+                    avg_error = RSSI_ERROR_VAL
+                    avg_shift = RSSI_ERROR_VAL
+                # Set Blackbox metric values
+                if self.publish_test_metrics:
+                    self.testcase_metric_logger.add_metric(
+                        '{}_error'.format(key), avg_error)
+                    self.testcase_metric_logger.add_metric(
+                        '{}_shift'.format(key), avg_shift)
+                # Evaluate test pass/fail
+                rssi_failure = (avg_error >
+                                self.testclass_params['abs_tolerance']
+                                ) or math.isnan(avg_error)
+                if rssi_failure and key in testcase_params['rssi_under_test']:
+                    test_message = test_message + (
+                        '{} failed ({} error = {:.2f} dB, '
+                        'shift = {:.2f} dB)\n').format(key, error_type,
+                                                       avg_error, avg_shift)
+                    test_failed = True
+                elif rssi_failure:
+                    test_message = test_message + (
+                        '{} failed (ignored) ({} error = {:.2f} dB, '
+                        'shift = {:.2f} dB)\n').format(key, error_type,
+                                                       avg_error, avg_shift)
+                else:
+                    test_message = test_message + (
+                        '{} passed ({} error = {:.2f} dB, '
+                        'shift = {:.2f} dB)\n').format(key, error_type,
+                                                       avg_error, avg_shift)
+        if test_failed:
+            asserts.fail(test_message)
+        asserts.explicit_pass(test_message)
+
+    def post_process_rssi_sweep(self, rssi_result):
+        """Postprocesses and saves JSON formatted results.
+
+        Args:
+            rssi_result: dict containing attenuation, rssi and other meta
+            data
+        Returns:
+            postprocessed_results: compiled arrays of RSSI data used in
+            pass/fail check
+        """
+        # Save output as text file
+        results_file_path = os.path.join(self.log_path, self.current_test_name)
+        with open(results_file_path, 'w') as results_file:
+            json.dump(rssi_result, results_file, indent=4)
+        # Compile results into arrays of RSSIs suitable for plotting
+        # yapf: disable
+        postprocessed_results = collections.OrderedDict(
+            [('signal_poll_rssi', {}),
+             ('signal_poll_avg_rssi', {}),
+             ('scan_rssi', {}),
+             ('chain_0_rssi', {}),
+             ('chain_1_rssi', {}),
+             ('total_attenuation', []),
+             ('predicted_rssi', [])])
+        # yapf: enable
+        for key, val in postprocessed_results.items():
+            if 'scan_rssi' in key:
+                postprocessed_results[key]['data'] = [
+                    x for data_point in rssi_result['rssi_result'] for x in
+                    data_point[key][rssi_result['connected_bssid']]['data']
+                ]
+                postprocessed_results[key]['mean'] = [
+                    x[key][rssi_result['connected_bssid']]['mean']
+                    for x in rssi_result['rssi_result']
+                ]
+                postprocessed_results[key]['stdev'] = [
+                    x[key][rssi_result['connected_bssid']]['stdev']
+                    for x in rssi_result['rssi_result']
+                ]
+            elif 'predicted_rssi' in key:
+                postprocessed_results['total_attenuation'] = [
+                    att + rssi_result['fixed_attenuation'] +
+                    rssi_result['dut_front_end_loss']
+                    for att in rssi_result['attenuation']
+                ]
+                postprocessed_results['predicted_rssi'] = [
+                    rssi_result['ap_tx_power'] - att
+                    for att in postprocessed_results['total_attenuation']
+                ]
+            elif 'rssi' in key:
+                postprocessed_results[key]['data'] = [
+                    x for data_point in rssi_result['rssi_result']
+                    for x in data_point[key]['data']
+                ]
+                postprocessed_results[key]['mean'] = [
+                    x[key]['mean'] for x in rssi_result['rssi_result']
+                ]
+                postprocessed_results[key]['stdev'] = [
+                    x[key]['stdev'] for x in rssi_result['rssi_result']
+                ]
+        # Compute RSSI errors
+        for key, val in postprocessed_results.items():
+            if 'chain' in key:
+                postprocessed_results[key]['error'] = [
+                    postprocessed_results[key]['mean'][idx] + CONST_3dB -
+                    postprocessed_results['predicted_rssi'][idx]
+                    for idx in range(
+                        len(postprocessed_results['predicted_rssi']))
+                ]
+            elif 'rssi' in key and 'predicted' not in key:
+                postprocessed_results[key]['error'] = [
+                    postprocessed_results[key]['mean'][idx] -
+                    postprocessed_results['predicted_rssi'][idx]
+                    for idx in range(
+                        len(postprocessed_results['predicted_rssi']))
+                ]
+        return postprocessed_results
+
+    def plot_rssi_vs_attenuation(self, postprocessed_results):
+        """Function to plot RSSI vs attenuation sweeps
+
+        Args:
+            postprocessed_results: compiled arrays of RSSI data.
+        """
+        figure = wputils.BokehFigure(self.current_test_name,
+                                     x_label='Attenuation (dB)',
+                                     primary_y_label='RSSI (dBm)')
+        figure.add_line(postprocessed_results['total_attenuation'],
+                        postprocessed_results['signal_poll_rssi']['mean'],
+                        'Signal Poll RSSI',
+                        marker='circle')
+        figure.add_line(postprocessed_results['total_attenuation'],
+                        postprocessed_results['scan_rssi']['mean'],
+                        'Scan RSSI',
+                        marker='circle')
+        figure.add_line(postprocessed_results['total_attenuation'],
+                        postprocessed_results['chain_0_rssi']['mean'],
+                        'Chain 0 RSSI',
+                        marker='circle')
+        figure.add_line(postprocessed_results['total_attenuation'],
+                        postprocessed_results['chain_1_rssi']['mean'],
+                        'Chain 1 RSSI',
+                        marker='circle')
+        figure.add_line(postprocessed_results['total_attenuation'],
+                        postprocessed_results['predicted_rssi'],
+                        'Predicted RSSI',
+                        marker='circle')
+
+        output_file_path = os.path.join(self.log_path,
+                                        self.current_test_name + '.html')
+        figure.generate_figure(output_file_path)
+
+    def plot_rssi_vs_time(self, rssi_result, postprocessed_results,
+                          center_curves):
+        """Function to plot RSSI vs time.
+
+        Args:
+            rssi_result: dict containing raw RSSI data
+            postprocessed_results: compiled arrays of RSSI data
+            center_curvers: boolean indicating whether to shift curves to align
+            them with predicted RSSIs
+        """
+        figure = wputils.BokehFigure(
+            self.current_test_name,
+            x_label='Time (s)',
+            primary_y_label=center_curves * 'Centered' + 'RSSI (dBm)',
+        )
+
+        # yapf: disable
+        rssi_time_series = collections.OrderedDict(
+            [('signal_poll_rssi', []),
+             ('signal_poll_avg_rssi', []),
+             ('scan_rssi', []),
+             ('chain_0_rssi', []),
+             ('chain_1_rssi', []),
+             ('predicted_rssi', [])])
+        # yapf: enable
+        for key, val in rssi_time_series.items():
+            if 'predicted_rssi' in key:
+                rssi_time_series[key] = [
+                    x for x in postprocessed_results[key] for copies in range(
+                        len(rssi_result['rssi_result'][0]['signal_poll_rssi']
+                            ['data']))
+                ]
+            elif 'rssi' in key:
+                if center_curves:
+                    filtered_error = [
+                        x for x in postprocessed_results[key]['error']
+                        if not math.isnan(x)
+                    ]
+                    if filtered_error:
+                        avg_shift = statistics.mean(filtered_error)
+                    else:
+                        avg_shift = 0
+                    rssi_time_series[key] = [
+                        x - avg_shift
+                        for x in postprocessed_results[key]['data']
+                    ]
+                else:
+                    rssi_time_series[key] = postprocessed_results[key]['data']
+            time_vec = [
+                self.testclass_params['polling_frequency'] * x
+                for x in range(len(rssi_time_series[key]))
+            ]
+            if len(rssi_time_series[key]) > 0:
+                figure.add_line(time_vec, rssi_time_series[key], key)
+
+        output_file_path = os.path.join(self.log_path,
+                                        self.current_test_name + '.html')
+        figure.generate_figure(output_file_path)
+
+    def plot_rssi_distribution(self, postprocessed_results):
+        """Function to plot RSSI distributions.
+
+        Args:
+            postprocessed_results: compiled arrays of RSSI data
+        """
+        monitored_rssis = ['signal_poll_rssi', 'chain_0_rssi', 'chain_1_rssi']
+
+        rssi_dist = collections.OrderedDict()
+        for rssi_key in monitored_rssis:
+            rssi_data = postprocessed_results[rssi_key]
+            rssi_dist[rssi_key] = collections.OrderedDict()
+            unique_rssi = sorted(set(rssi_data['data']))
+            rssi_counts = []
+            for value in unique_rssi:
+                rssi_counts.append(rssi_data['data'].count(value))
+            total_count = sum(rssi_counts)
+            rssi_dist[rssi_key]['rssi_values'] = unique_rssi
+            rssi_dist[rssi_key]['rssi_pdf'] = [
+                x / total_count for x in rssi_counts
+            ]
+            rssi_dist[rssi_key]['rssi_cdf'] = []
+            cum_prob = 0
+            for prob in rssi_dist[rssi_key]['rssi_pdf']:
+                cum_prob += prob
+                rssi_dist[rssi_key]['rssi_cdf'].append(cum_prob)
+
+        figure = wputils.BokehFigure(self.current_test_name,
+                                     x_label='RSSI (dBm)',
+                                     primary_y_label='p(RSSI = x)',
+                                     secondary_y_label='p(RSSI <= x)')
+        for rssi_key, rssi_data in rssi_dist.items():
+            figure.add_line(x_data=rssi_data['rssi_values'],
+                            y_data=rssi_data['rssi_pdf'],
+                            legend='{} PDF'.format(rssi_key),
+                            y_axis='default')
+            figure.add_line(x_data=rssi_data['rssi_values'],
+                            y_data=rssi_data['rssi_cdf'],
+                            legend='{} CDF'.format(rssi_key),
+                            y_axis='secondary')
+        output_file_path = os.path.join(self.log_path,
+                                        self.current_test_name + '_dist.html')
+        figure.generate_figure(output_file_path)
+
+    def run_rssi_test(self, testcase_params):
+        """Test function to run RSSI tests.
+
+        The function runs an RSSI test in the current device/AP configuration.
+        Function is called from another wrapper function that sets up the
+        testbed for the RvR test
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        Returns:
+            rssi_result: dict containing rssi_result and meta data
+        """
+        # Run test and log result
+        rssi_result = collections.OrderedDict()
+        rssi_result['test_name'] = self.current_test_name
+        rssi_result['testcase_params'] = testcase_params
+        rssi_result['ap_settings'] = self.access_point.ap_settings.copy()
+        rssi_result['attenuation'] = list(testcase_params['rssi_atten_range'])
+        rssi_result['connected_bssid'] = self.main_network[
+            testcase_params['band']].get('BSSID', '00:00:00:00')
+        channel_mode_combo = '{}_{}'.format(str(testcase_params['channel']),
+                                            testcase_params['mode'])
+        channel_str = str(testcase_params['channel'])
+        if channel_mode_combo in self.testbed_params['ap_tx_power']:
+            rssi_result['ap_tx_power'] = self.testbed_params['ap_tx_power'][
+                channel_mode_combo]
+        else:
+            rssi_result['ap_tx_power'] = self.testbed_params['ap_tx_power'][
+                str(testcase_params['channel'])]
+        rssi_result['fixed_attenuation'] = self.testbed_params[
+            'fixed_attenuation'][channel_str]
+        rssi_result['dut_front_end_loss'] = self.testbed_params[
+            'dut_front_end_loss'][channel_str]
+
+        self.log.info('Start running RSSI test.')
+        rssi_result['rssi_result'] = []
+        rssi_result['llstats'] = []
+        llstats_obj = wputils.LinkLayerStats(self.dut)
+        # Start iperf traffic if required by test
+        if testcase_params['active_traffic'] and testcase_params[
+                'traffic_type'] == 'iperf':
+            self.iperf_server.start(tag=0)
+            if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+                iperf_server_address = self.dut_ip
+            else:
+                iperf_server_address = wputils.get_server_address(
+                    self.remote_server, self.dut_ip, '255.255.255.0')
+            executor = ThreadPoolExecutor(max_workers=1)
+            thread_future = executor.submit(
+                self.iperf_client.start, iperf_server_address,
+                testcase_params['iperf_args'], 0,
+                testcase_params['traffic_timeout'] + SHORT_SLEEP)
+            executor.shutdown(wait=False)
+        elif testcase_params['active_traffic'] and testcase_params[
+                'traffic_type'] == 'ping':
+            thread_future = wputils.get_ping_stats_nb(
+                self.remote_server, self.dut_ip,
+                testcase_params['traffic_timeout'], 0.02, 64)
+        else:
+            thread_future = wputils.get_ping_stats_nb(
+                self.remote_server, self.dut_ip,
+                testcase_params['traffic_timeout'], 0.5, 64)
+        for atten in testcase_params['rssi_atten_range']:
+            # Set Attenuation
+            self.log.info('Setting attenuation to {} dB'.format(atten))
+            for attenuator in self.attenuators:
+                attenuator.set_atten(atten)
+            llstats_obj.update_stats()
+            current_rssi = collections.OrderedDict()
+            current_rssi = wputils.get_connected_rssi(
+                self.dut, testcase_params['connected_measurements'],
+                self.testclass_params['polling_frequency'],
+                testcase_params['first_measurement_delay'])
+            current_rssi['scan_rssi'] = wputils.get_scan_rssi(
+                self.dut, testcase_params['tracked_bssid'],
+                testcase_params['scan_measurements'])
+            rssi_result['rssi_result'].append(current_rssi)
+            llstats_obj.update_stats()
+            curr_llstats = llstats_obj.llstats_incremental.copy()
+            rssi_result['llstats'].append(curr_llstats)
+            self.log.info(
+                'Connected RSSI at {0:.2f} dB is {1:.2f} [{2:.2f}, {3:.2f}] dB'
+                .format(atten, current_rssi['signal_poll_rssi']['mean'],
+                        current_rssi['chain_0_rssi']['mean'],
+                        current_rssi['chain_1_rssi']['mean']))
+        # Stop iperf traffic if needed
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0)
+        thread_future.result()
+        if testcase_params['active_traffic'] and testcase_params[
+                'traffic_type'] == 'iperf':
+            self.iperf_server.stop()
+        return rssi_result
+
+    def setup_ap(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        if '2G' in testcase_params['band']:
+            frequency = wutils.WifiEnums.channel_2G_to_freq[
+                testcase_params['channel']]
+        else:
+            frequency = wutils.WifiEnums.channel_5G_to_freq[
+                testcase_params['channel']]
+        if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
+            self.access_point.set_region(self.testbed_params['DFS_region'])
+        else:
+            self.access_point.set_region(self.testbed_params['default_region'])
+        self.access_point.set_channel(testcase_params['band'],
+                                      testcase_params['channel'])
+        self.access_point.set_bandwidth(testcase_params['band'],
+                                        testcase_params['mode'])
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test."""
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('Battery level too low. Skipping test.')
+        # Turn screen off to preserve battery
+        self.dut.go_to_sleep()
+        if wputils.validate_network(self.dut,
+                                    testcase_params['test_network']['SSID']):
+            self.log.info('Already connected to desired network')
+        else:
+            wutils.wifi_toggle_state(self.dut, True)
+            wutils.reset_wifi(self.dut)
+            self.main_network[testcase_params['band']][
+                'channel'] = testcase_params['channel']
+            wutils.set_wifi_country_code(self.dut,
+                                         self.testclass_params['country_code'])
+            wutils.wifi_connect(self.dut,
+                                self.main_network[testcase_params['band']],
+                                num_of_tries=5)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+    def setup_rssi_test(self, testcase_params):
+        """Main function to test RSSI.
+
+        The function sets up the AP in the correct channel and mode
+        configuration and called rssi_test to sweep attenuation and measure
+        RSSI
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        Returns:
+            rssi_result: dict containing rssi_results and meta data
+        """
+        # Configure AP
+        self.setup_ap(testcase_params)
+        # Initialize attenuators
+        for attenuator in self.attenuators:
+            attenuator.set_atten(testcase_params['rssi_atten_range'][0])
+        # Connect DUT to Network
+        self.setup_dut(testcase_params)
+
+    def get_traffic_timeout(self, testcase_params):
+        """Function to comput iperf session length required in RSSI test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        Returns:
+            traffic_timeout: length of iperf session required in rssi test
+        """
+        atten_step_duration = testcase_params['first_measurement_delay'] + (
+            testcase_params['connected_measurements'] *
+            self.testclass_params['polling_frequency']
+        ) + testcase_params['scan_measurements'] * MED_SLEEP
+        timeout = len(testcase_params['rssi_atten_range']
+                      ) * atten_step_duration + MED_SLEEP
+        return timeout
+
+    def compile_rssi_vs_atten_test_params(self, testcase_params):
+        """Function to complete compiling test-specific parameters
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        testcase_params.update(
+            connected_measurements=self.
+            testclass_params['rssi_vs_atten_connected_measurements'],
+            scan_measurements=self.
+            testclass_params['rssi_vs_atten_scan_measurements'],
+            first_measurement_delay=MED_SLEEP,
+            rssi_under_test=self.testclass_params['rssi_vs_atten_metrics'],
+            absolute_accuracy=1)
+
+        testcase_params['band'] = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[
+            testcase_params['band']]
+        testcase_params['tracked_bssid'] = [
+            self.main_network[testcase_params['band']].get(
+                'BSSID', '00:00:00:00')
+        ]
+
+        num_atten_steps = int((self.testclass_params['rssi_vs_atten_stop'] -
+                               self.testclass_params['rssi_vs_atten_start']) /
+                              self.testclass_params['rssi_vs_atten_step'])
+        testcase_params['rssi_atten_range'] = [
+            self.testclass_params['rssi_vs_atten_start'] +
+            x * self.testclass_params['rssi_vs_atten_step']
+            for x in range(0, num_atten_steps)
+        ]
+        testcase_params['traffic_timeout'] = self.get_traffic_timeout(
+            testcase_params)
+
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
+                testcase_params['traffic_timeout'])
+        else:
+            testcase_params['iperf_args'] = '-i 1 -t {} -J -R'.format(
+                testcase_params['traffic_timeout'])
+        return testcase_params
+
+    def compile_rssi_stability_test_params(self, testcase_params):
+        """Function to complete compiling test-specific parameters
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        testcase_params.update(
+            connected_measurements=int(
+                self.testclass_params['rssi_stability_duration'] /
+                self.testclass_params['polling_frequency']),
+            scan_measurements=0,
+            first_measurement_delay=MED_SLEEP,
+            rssi_atten_range=self.testclass_params['rssi_stability_atten'])
+        testcase_params['band'] = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[
+            testcase_params['band']]
+        testcase_params['tracked_bssid'] = [
+            self.main_network[testcase_params['band']].get(
+                'BSSID', '00:00:00:00')
+        ]
+
+        testcase_params['traffic_timeout'] = self.get_traffic_timeout(
+            testcase_params)
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
+                testcase_params['traffic_timeout'])
+        else:
+            testcase_params['iperf_args'] = '-i 1 -t {} -J -R'.format(
+                testcase_params['traffic_timeout'])
+        return testcase_params
+
+    def compile_rssi_tracking_test_params(self, testcase_params):
+        """Function to complete compiling test-specific parameters
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        testcase_params.update(connected_measurements=int(
+            1 / self.testclass_params['polling_frequency']),
+                               scan_measurements=0,
+                               first_measurement_delay=0,
+                               rssi_under_test=['signal_poll_rssi'],
+                               absolute_accuracy=0)
+        testcase_params['band'] = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[
+            testcase_params['band']]
+        testcase_params['tracked_bssid'] = [
+            self.main_network[testcase_params['band']].get(
+                'BSSID', '00:00:00:00')
+        ]
+
+        rssi_atten_range = []
+        for waveform in self.testclass_params['rssi_tracking_waveforms']:
+            waveform_vector = []
+            for section in range(len(waveform['atten_levels']) - 1):
+                section_limits = waveform['atten_levels'][section:section + 2]
+                up_down = (1 - 2 * (section_limits[1] < section_limits[0]))
+                temp_section = list(
+                    range(section_limits[0], section_limits[1] + up_down,
+                          up_down * waveform['step_size']))
+                temp_section = [
+                    temp_section[idx] for idx in range(len(temp_section))
+                    for n in range(waveform['step_duration'])
+                ]
+                waveform_vector += temp_section
+            waveform_vector = waveform_vector * waveform['repetitions']
+            rssi_atten_range = rssi_atten_range + waveform_vector
+        testcase_params['rssi_atten_range'] = rssi_atten_range
+        testcase_params['traffic_timeout'] = self.get_traffic_timeout(
+            testcase_params)
+
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
+                testcase_params['traffic_timeout'])
+        else:
+            testcase_params['iperf_args'] = '-i 1 -t {} -J -R'.format(
+                testcase_params['traffic_timeout'])
+        return testcase_params
+
+    def _test_rssi_vs_atten(self, testcase_params):
+        """Function that gets called for each test case of rssi_vs_atten
+
+        The function gets called in each rssi test case. The function
+        customizes the test based on the test name of the test that called it
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        testcase_params = self.compile_rssi_vs_atten_test_params(
+            testcase_params)
+
+        self.setup_rssi_test(testcase_params)
+        rssi_result = self.run_rssi_test(testcase_params)
+        rssi_result['postprocessed_results'] = self.post_process_rssi_sweep(
+            rssi_result)
+        self.testclass_results.append(rssi_result)
+        self.plot_rssi_vs_attenuation(rssi_result['postprocessed_results'])
+        self.pass_fail_check_rssi_accuracy(
+            testcase_params, rssi_result['postprocessed_results'])
+
+    def _test_rssi_stability(self, testcase_params):
+        """ Function that gets called for each test case of rssi_stability
+
+        The function gets called in each stability test case. The function
+        customizes test based on the test name of the test that called it
+        """
+        testcase_params = self.compile_rssi_stability_test_params(
+            testcase_params)
+
+        self.setup_rssi_test(testcase_params)
+        rssi_result = self.run_rssi_test(testcase_params)
+        rssi_result['postprocessed_results'] = self.post_process_rssi_sweep(
+            rssi_result)
+        self.testclass_results.append(rssi_result)
+        self.plot_rssi_vs_time(rssi_result,
+                               rssi_result['postprocessed_results'], 1)
+        self.plot_rssi_distribution(rssi_result['postprocessed_results'])
+        self.pass_fail_check_rssi_stability(
+            testcase_params, rssi_result['postprocessed_results'])
+
+    def _test_rssi_tracking(self, testcase_params):
+        """ Function that gets called for each test case of rssi_tracking
+
+        The function gets called in each rssi test case. The function
+        customizes the test based on the test name of the test that called it
+        """
+        testcase_params = self.compile_rssi_tracking_test_params(
+            testcase_params)
+
+        self.setup_rssi_test(testcase_params)
+        rssi_result = self.run_rssi_test(testcase_params)
+        rssi_result['postprocessed_results'] = self.post_process_rssi_sweep(
+            rssi_result)
+        self.testclass_results.append(rssi_result)
+        self.plot_rssi_vs_time(rssi_result,
+                               rssi_result['postprocessed_results'], 1)
+        self.pass_fail_check_rssi_accuracy(
+            testcase_params, rssi_result['postprocessed_results'])
+
+    def generate_test_cases(self, test_types, channels, modes, traffic_modes):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+
+        for channel, mode, traffic_mode, test_type in itertools.product(
+                channels, modes, traffic_modes, test_types):
+            if channel not in allowed_configs[mode]:
+                continue
+            test_name = test_type + '_ch{}_{}_{}'.format(
+                channel, mode, traffic_mode)
+            testcase_params = collections.OrderedDict(
+                channel=channel,
+                mode=mode,
+                active_traffic=(traffic_mode == 'ActiveTraffic'),
+                traffic_type=self.user_params['rssi_test_params']
+                ['traffic_type'],
+            )
+            test_function = getattr(self, '_{}'.format(test_type))
+            setattr(self, test_name, partial(test_function, testcase_params))
+            test_cases.append(test_name)
+        return test_cases
+
+
+class WifiRssi_2GHz_ActiveTraffic_Test(WifiRssiTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ['test_rssi_stability', 'test_rssi_vs_atten'], [1, 2, 6, 10, 11],
+            ['VHT20'], ['ActiveTraffic'])
+
+
+class WifiRssi_5GHz_ActiveTraffic_Test(WifiRssiTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ['test_rssi_stability', 'test_rssi_vs_atten'],
+            [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
+            ['ActiveTraffic'])
+
+
+class WifiRssi_AllChannels_ActiveTraffic_Test(WifiRssiTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ['test_rssi_stability', 'test_rssi_vs_atten'],
+            [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            ['VHT20', 'VHT40', 'VHT80'], ['ActiveTraffic'])
+
+
+class WifiRssi_SampleChannels_NoTraffic_Test(WifiRssiTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            ['test_rssi_stability', 'test_rssi_vs_atten'], [6, 36, 149],
+            ['VHT20', 'VHT40', 'VHT80'], ['NoTraffic'])
+
+
+class WifiRssiTrackingTest(WifiRssiTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(['test_rssi_tracking'],
+                                              [6, 36, 149],
+                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['ActiveTraffic', 'NoTraffic'])
+
+
+# Over-the air version of RSSI tests
+class WifiOtaRssiTest(WifiRssiTest):
+    """Class to test over-the-air rssi tests.
+
+    This class implements measures WiFi RSSI tests in an OTA chamber.
+    It allows setting orientation and other chamber parameters to study
+    performance in varying channel conditions
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_test_metrics = False
+
+    def setup_class(self):
+        WifiRssiTest.setup_class(self)
+        self.ota_chamber = ota_chamber.create(
+            self.user_params['OTAChamber'])[0]
+
+    def teardown_class(self):
+        self.ota_chamber.reset_chamber()
+        self.process_testclass_results()
+
+    def teardown_test(self):
+        if self.ota_chamber.current_mode == 'continuous':
+            self.ota_chamber.reset_chamber()
+
+    def extract_test_id(self, testcase_params, id_fields):
+        test_id = collections.OrderedDict(
+            (param, testcase_params[param]) for param in id_fields)
+        return test_id
+
+    def process_testclass_results(self):
+        """Saves all test results to enable comparison."""
+        testclass_data = collections.OrderedDict()
+        for test_result in self.testclass_results:
+            current_params = test_result['testcase_params']
+
+            channel = current_params['channel']
+            channel_data = testclass_data.setdefault(
+                channel,
+                collections.OrderedDict(orientation=[],
+                                        rssi=collections.OrderedDict(
+                                            signal_poll_rssi=[],
+                                            chain_0_rssi=[],
+                                            chain_1_rssi=[])))
+
+            channel_data['orientation'].append(current_params['orientation'])
+            channel_data['rssi']['signal_poll_rssi'].append(
+                test_result['postprocessed_results']['signal_poll_rssi']
+                ['mean'][0])
+            channel_data['rssi']['chain_0_rssi'].append(
+                test_result['postprocessed_results']['chain_0_rssi']['mean']
+                [0])
+            channel_data['rssi']['chain_1_rssi'].append(
+                test_result['postprocessed_results']['chain_1_rssi']['mean']
+                [0])
+
+        # Publish test class metrics
+        for channel, channel_data in testclass_data.items():
+            for rssi_metric, rssi_metric_value in channel_data['rssi'].items():
+                metric_name = 'ota_summary_ch{}.avg_{}'.format(
+                    channel, rssi_metric)
+                metric_value = numpy.mean(rssi_metric_value)
+                self.testclass_metric_logger.add_metric(
+                    metric_name, metric_value)
+
+        # Plot test class results
+        chamber_mode = self.testclass_results[0]['testcase_params'][
+            'chamber_mode']
+        if chamber_mode == 'orientation':
+            x_label = 'Angle (deg)'
+        elif chamber_mode == 'stepped stirrers':
+            x_label = 'Position Index'
+        elif chamber_mode == 'StirrersOn':
+            return
+        plots = []
+        for channel, channel_data in testclass_data.items():
+            current_plot = wputils.BokehFigure(
+                title='Channel {} - Rssi vs. Position'.format(channel),
+                x_label=x_label,
+                primary_y_label='RSSI (dBm)',
+            )
+            for rssi_metric, rssi_metric_value in channel_data['rssi'].items():
+                legend = rssi_metric
+                current_plot.add_line(channel_data['orientation'],
+                                      rssi_metric_value, legend)
+            current_plot.generate_figure()
+            plots.append(current_plot)
+        current_context = context.get_current_context().get_full_output_path()
+        plot_file_path = os.path.join(current_context, 'results.html')
+        wputils.BokehFigure.save_figures(plots, plot_file_path)
+
+    def setup_rssi_test(self, testcase_params):
+        # Test setup
+        WifiRssiTest.setup_rssi_test(self, testcase_params)
+        if testcase_params['chamber_mode'] == 'StirrersOn':
+            self.ota_chamber.start_continuous_stirrers()
+        else:
+            self.ota_chamber.set_orientation(testcase_params['orientation'])
+
+    def compile_ota_rssi_test_params(self, testcase_params):
+        """Function to complete compiling test-specific parameters
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        if "rssi_over_orientation" in self.test_name:
+            rssi_test_duration = self.testclass_params[
+                'rssi_over_orientation_duration']
+        elif "rssi_variation" in self.test_name:
+            rssi_test_duration = self.testclass_params[
+                'rssi_variation_duration']
+
+        testcase_params.update(
+            connected_measurements=int(
+                rssi_test_duration /
+                self.testclass_params['polling_frequency']),
+            scan_measurements=0,
+            first_measurement_delay=MED_SLEEP,
+            rssi_atten_range=[
+                self.testclass_params['rssi_ota_test_attenuation']
+            ])
+        testcase_params['band'] = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[
+            testcase_params['band']]
+        testcase_params['tracked_bssid'] = [
+            self.main_network[testcase_params['band']].get(
+                'BSSID', '00:00:00:00')
+        ]
+
+        testcase_params['traffic_timeout'] = self.get_traffic_timeout(
+            testcase_params)
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
+                testcase_params['traffic_timeout'])
+        else:
+            testcase_params['iperf_args'] = '-i 1 -t {} -J -R'.format(
+                testcase_params['traffic_timeout'])
+        return testcase_params
+
+    def _test_ota_rssi(self, testcase_params):
+        testcase_params = self.compile_ota_rssi_test_params(testcase_params)
+
+        self.setup_rssi_test(testcase_params)
+        rssi_result = self.run_rssi_test(testcase_params)
+        rssi_result['postprocessed_results'] = self.post_process_rssi_sweep(
+            rssi_result)
+        self.testclass_results.append(rssi_result)
+        self.plot_rssi_vs_time(rssi_result,
+                               rssi_result['postprocessed_results'], 1)
+        self.plot_rssi_distribution(rssi_result['postprocessed_results'])
+
+    def generate_test_cases(self, test_types, channels, modes, traffic_modes,
+                            chamber_modes, orientations):
+        test_cases = []
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+
+        for (channel, mode, traffic, chamber_mode, orientation,
+             test_type) in itertools.product(channels, modes, traffic_modes,
+                                             chamber_modes, orientations,
+                                             test_types):
+            if channel not in allowed_configs[mode]:
+                continue
+            test_name = test_type + '_ch{}_{}_{}_{}deg'.format(
+                channel, mode, traffic, orientation)
+            testcase_params = collections.OrderedDict(
+                channel=channel,
+                mode=mode,
+                active_traffic=(traffic == 'ActiveTraffic'),
+                traffic_type=self.user_params['rssi_test_params']
+                ['traffic_type'],
+                chamber_mode=chamber_mode,
+                orientation=orientation)
+            test_function = self._test_ota_rssi
+            setattr(self, test_name, partial(test_function, testcase_params))
+            test_cases.append(test_name)
+        return test_cases
+
+
+class WifiOtaRssi_Accuracy_Test(WifiOtaRssiTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(['test_rssi_vs_atten'],
+                                              [6, 36, 149], ['VHT20'],
+                                              ['ActiveTraffic'],
+                                              ['orientation'],
+                                              list(range(0, 360, 45)))
+
+
+class WifiOtaRssi_StirrerVariation_Test(WifiOtaRssiTest):
+    def __init__(self, controllers):
+        WifiRssiTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(['test_rssi_variation'],
+                                              [6, 36, 149], ['VHT20'],
+                                              ['ActiveTraffic'],
+                                              ['StirrersOn'], [0])
+
+
+class WifiOtaRssi_TenDegree_Test(WifiOtaRssiTest):
+    def __init__(self, controllers):
+        WifiRssiTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(['test_rssi_over_orientation'],
+                                              [6, 36, 149], ['VHT20'],
+                                              ['ActiveTraffic'],
+                                              ['orientation'],
+                                              list(range(0, 360, 10)))
diff --git a/acts_tests/tests/google/wifi/WifiRttManagerTest.py b/acts_tests/tests/google/wifi/WifiRttManagerTest.py
new file mode 100644
index 0000000..f0985de
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiRttManagerTest.py
@@ -0,0 +1,574 @@
+#!/usr/bin/env python3.4
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import pprint
+import queue
+
+import acts.base_test
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+from acts import asserts
+from acts.controllers.sl4a_lib import rpc_client
+
+WifiEnums = wutils.WifiEnums
+
+# Macros for RttParam keywords
+RttParam = WifiEnums.RttParam
+# Macros for RttManager
+Rtt = WifiEnums.Rtt
+RttBW = WifiEnums.RttBW
+RttPreamble = WifiEnums.RttPreamble
+RttPeerType = WifiEnums.RttPeerType
+RttType = WifiEnums.RttType
+
+ScanResult = WifiEnums.ScanResult
+RTT_MARGIN_OF_ERROR = WifiEnums.RTT_MARGIN_OF_ERROR
+
+
+class WifiRTTRangingError(Exception):
+    """Error in WifiScanner Rtt."""
+
+
+class WifiRttManagerTest(acts.base_test.BaseTestClass):
+    """Tests for wifi's RttManager APIs."""
+    tests = None
+    MAX_RTT_AP = 10
+
+    def __init__(self, controllers):
+        acts.base_test.BaseTestClass.__init__(self, controllers)
+        self.tests = ("test_support_check", "test_invalid_params",
+                      "test_capability_check",
+                      "test_rtt_ranging_single_AP_stress",
+                      "test_regular_scan_then_rtt_ranging_stress",
+                      "test_gscan_then_rtt_ranging_stress")
+
+    def setup_class(self):
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        required_params = ("support_models", "stress_num", "vht80_5g",
+                           "actual_distance")
+        self.unpack_userparams(required_params)
+        asserts.assert_true(
+            self.actual_distance >= 5,
+            "Actual distance should be no shorter than 5 meters.")
+        self.visible_networks = (self.vht80_5g, )
+        self.default_rtt_params = {
+            RttParam.request_type: RttType.TYPE_TWO_SIDED,
+            RttParam.device_type: RttPeerType.PEER_TYPE_AP,
+            RttParam.preamble: RttPreamble.PREAMBLE_HT,
+            RttParam.bandwidth: RttBW.BW_80_SUPPORT
+        }
+        # Expected capability for devices that don't support RTT.
+        rtt_cap_neg = {
+            'lcrSupported': False,
+            'bwSupported': 0,
+            'twoSided11McRttSupported': False,
+            'preambleSupported': 0,
+            'oneSidedRttSupported': False,
+            'lciSupported': False
+        }
+        rtt_cap_shamu = {
+            'lcrSupported': False,
+            'bwSupported': 0x1C,
+            'twoSided11McRttSupported': True,
+            'preambleSupported': 6,
+            'oneSidedRttSupported': False,
+            'lciSupported': False
+        }
+        rtt_cap_bullhead = {
+            'lcrSupported': True,
+            'bwSupported': 0x1C,
+            'twoSided11McRttSupported': True,
+            'preambleSupported': 7,
+            'oneSidedRttSupported': True,
+            'lciSupported': True
+        }
+        rtt_cap_angler = {
+            'lcrSupported': True,
+            'bwSupported': 0x1C,
+            'twoSided11McRttSupported': True,
+            'preambleSupported': 6,
+            'oneSidedRttSupported': False,
+            'lciSupported': True
+        }
+        self.rtt_cap_table = {
+            "hammerhead": rtt_cap_neg,
+            "shamu": rtt_cap_shamu,
+            "volantis": rtt_cap_neg,
+            "volantisg": rtt_cap_neg,
+            "bullhead": rtt_cap_bullhead,
+            "angler": rtt_cap_angler
+        }
+
+    """Helper Functions"""
+
+    def invalid_params_logic(self, rtt_params):
+        try:
+            self.dut.droid.wifiRttStartRanging([rtt_params])
+        except rpc_client.Sl4aApiError as e:
+            e_str = str(e)
+            asserts.assert_true(
+                "IllegalArgumentException" in e_str,
+                "Missing IllegalArgumentException in %s." % e_str)
+            msg = "Got expected exception with invalid param %s." % rtt_params
+            self.log.info(msg)
+
+    def get_rtt_results(self, rtt_params):
+        """Starts RTT ranging and get results.
+
+        Args:
+            rtt_params: A list of dicts each representing an RttParam.
+
+        Returns:
+            Rtt ranging results.
+        """
+        self.log.debug("Start ranging with:\n%s" % pprint.pformat(rtt_params))
+        idx = self.dut.droid.wifiRttStartRanging(rtt_params)
+        event = None
+        try:
+            event = self.dut.ed.pop_events("WifiRttRanging%d" % idx, 30)
+            if event[0]["name"].endswith("onSuccess"):
+                results = event[0]["data"]["Results"]
+                result_len = len(results)
+                param_len = len(rtt_params)
+                asserts.assert_true(result_len == param_len,
+                                    "Expected %d results, got %d." %
+                                    (param_len, result_len))
+                # Add acceptable margin of error to results, which will be used
+                # during result processing.
+                for i, r in enumerate(results):
+                    bw_mode = rtt_params[i][RttParam.bandwidth]
+                    r[RttParam.margin] = RTT_MARGIN_OF_ERROR[bw_mode]
+            self.log.debug(pprint.pformat(event))
+            return event
+        except queue.Empty:
+            self.log.error("Waiting for RTT event timed out.")
+            return None
+
+    def network_selector(self, network_info):
+        """Decides if a network should be used for rtt ranging.
+
+        There are a few conditions:
+        1. This network supports 80211mc.
+        2. This network's info matches certain conditions.
+
+        This is added to better control which networks to range against instead
+        of blindly use all 80211mc networks in air.
+
+        Args:
+            network_info: A dict representing a WiFi network.
+
+        Returns:
+            True if the input network should be used for ranging, False
+            otherwise.
+        """
+        target_params = {
+            "is80211McRTTResponder": True,
+            WifiEnums.BSSID_KEY: self.vht80_5g[WifiEnums.BSSID_KEY],
+        }
+        for k, v in target_params.items():
+            if k not in network_info:
+                return False
+            if type(network_info[k]) is str:
+                network_info[k] = network_info[k].lower()
+                v = v.lower()
+            if network_info[k] != v:
+                return False
+        return True
+
+    def regular_scan_for_rtt_networks(self):
+        """Scans for 11mc-capable WiFi networks using regular wifi scan.
+
+        Networks are selected based on self.network_selector.
+
+        Returns:
+            A list of networks that have RTTResponders.
+        """
+        wutils.start_wifi_connection_scan(self.dut)
+        networks = self.dut.droid.wifiGetScanResults()
+        rtt_networks = []
+        for nw in networks:
+            if self.network_selector(nw):
+                rtt_networks.append(nw)
+        return rtt_networks
+
+    def gscan_for_rtt_networks(self):
+        """Scans for 11mc-capable WiFi networks using wifi gscan.
+
+        Networks are selected based on self.network_selector.
+
+        Returns:
+            A list of networks that have RTTResponders.
+        """
+        s = {
+            "reportEvents": WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT,
+            "band": WifiEnums.WIFI_BAND_BOTH,
+            "periodInMs": 10000,
+            "numBssidsPerScan": 32
+        }
+        idx = wutils.start_wifi_single_scan(self.android_devices[0],
+                                            s)["Index"]
+        self.log.info("Scan index is %d" % idx)
+        event_name = "WifiScannerScan%donFullResult" % idx
+
+        def condition(event):
+            nw = event["data"]["Results"][0]
+            return self.network_selector(nw)
+
+        rtt_networks = []
+        try:
+            for i in range(len(self.visible_networks)):
+                event = self.dut.ed.wait_for_event(event_name, condition, 30)
+                rtt_networks.append(event["data"]["Results"][0])
+            self.log.info("Waiting for gscan to finish.")
+            event_name = "WifiScannerScan%donResults" % idx
+            event = self.dut.ed.pop_event(event_name, 300)
+            total_network_cnt = len(event["data"]["Results"][0]["ScanResults"])
+            self.log.info("Found %d networks in total." % total_network_cnt)
+            self.log.debug(rtt_networks)
+            return rtt_networks
+        except queue.Empty:
+            self.log.error("Timed out waiting for gscan result.")
+
+    def process_rtt_events(self, events):
+        """Processes rtt ranging events.
+
+        Validates RTT event types.
+        Validates RTT response status and measured RTT values.
+        Enforces success rate.
+
+        Args:
+            events: A list of callback results from RTT ranging.
+        """
+        total = aborted = failure = invalid = out_of_range = 0
+        for e in events:
+            if e["name"].endswith("onAborted"):
+                aborted += 1
+            if e["name"].endswith("onFailure"):
+                failure += 1
+            if e["name"].endswith("onSuccess"):
+                results = e["data"]["Results"]
+                for r in results:
+                    total += 1
+                    # Status needs to be "success".
+                    status = r["status"]
+                    if status != Rtt.STATUS_SUCCESS:
+                        self.log.warning("Got error status %d." % status)
+                        invalid += 1
+                        continue
+                    # RTT value should be positive.
+                    value = r["rtt"]
+                    if value <= 0:
+                        self.log.warning("Got error RTT value %d." % value)
+                        invalid += 1
+                        continue
+                    # Vadlidate values in successful responses.
+                    acd = self.actual_distance
+                    margin = r[RttParam.margin]
+                    # If the distance is >= 0, check distance only.
+                    d = r["distance"] / 100.0
+                    if d > 0:
+                        # Distance should be in acceptable range.
+                        is_d_valid = (acd - margin) <= d <= acd + (margin)
+                        if not is_d_valid:
+                            self.log.warning(
+                                ("Reported distance %.2fm is out of the"
+                                 " acceptable range %.2f±%.2fm.") % (d, acd,
+                                                                     margin))
+                            out_of_range += 1
+                        continue
+                    # Check if the RTT value is in range.
+                    d = (value / 2) / 1E10 * wutils.SPEED_OF_LIGHT
+                    is_rtt_valid = (acd - margin) <= d <= (acd + margin)
+                    if not is_rtt_valid:
+                        self.log.warning((
+                            "Distance calculated from RTT value %d - %.2fm is "
+                            "out of the acceptable range %.2f±%dm.") %
+                                         (value, d, acd, margin))
+                        out_of_range += 1
+                        continue
+                    # Check if the RSSI value is in range.
+                    rssi = r["rssi"]
+                    # average rssi in 0.5dB steps, e.g. 143 implies -71.5dB,
+                    # so the valid range is 0 to 200
+                    is_rssi_valid = 0 <= rssi <= 200
+                    if not is_rssi_valid:
+                        self.log.warning(("Reported RSSI %d is out of the"
+                                          " acceptable range 0-200") % rssi)
+                        out_of_range += 1
+                        continue
+        self.log.info((
+            "Processed %d RTT events. %d aborted, %s failed. Among"
+            " the %d responses in successful callbacks, %s are invalid, %s has"
+            " RTT values that are out of range.") %
+                      (len(events), aborted, failure, total, invalid,
+                       out_of_range))
+        asserts.assert_true(total > 0, "No RTT response received.")
+        # Percentage of responses that are valid should be >= 90%.
+        valid_total = float(total - invalid)
+        valid_response_rate = valid_total / total
+        self.log.info("%.2f%% of the responses are valid." %
+                      (valid_response_rate * 100))
+        asserts.assert_true(valid_response_rate >= 0.9,
+                            "Valid response rate is below 90%%.")
+        # Among the valid responses, the percentage of having an in-range RTT
+        # value should be >= 67%.
+        valid_value_rate = (total - invalid - out_of_range) / valid_total
+        self.log.info("%.2f%% of valid responses have in-range RTT value" %
+                      (valid_value_rate * 100))
+        msg = "In-range response rate is below 67%%."
+        asserts.assert_true(valid_value_rate >= 0.67, msg)
+
+    def scan_then_rtt_ranging_stress_logic(self, scan_func):
+        """Test logic to scan then do rtt ranging based on the scan results.
+
+        Steps:
+        1. Start scan and get scan results.
+        2. Filter out the networks that support rtt in scan results.
+        3. Start rtt ranging against those networks that support rtt.
+        4. Repeat
+        5. Process RTT events.
+
+        Args:
+            scan_func: A function that does a wifi scan and only returns the
+                networks that support rtt in the scan results.
+
+        Returns:
+            True if rtt behaves as expected, False otherwise.
+        """
+        total = self.stress_num
+        failed = 0
+        all_results = []
+        for i in range(total):
+            self.log.info("Iteration %d" % i)
+            rtt_networks = scan_func()
+            if not rtt_networks:
+                self.log.warning("Found no rtt network, skip this iteration.")
+                failed += 1
+                continue
+            self.log.debug("Found rtt networks:%s" % rtt_networks)
+            rtt_params = []
+            for rn in rtt_networks:
+                rtt_params.append(self.rtt_config_from_scan_result(rn))
+            results = self.get_rtt_results(rtt_params)
+            if results:
+                self.log.debug(results)
+                all_results += results
+        self.process_rtt_events(all_results)
+
+    def rtt_config_from_scan_result(self, scan_result):
+        """Creates an Rtt configuration based on the scan result of a network.
+        """
+        scan_result_channel_width_to_rtt = {
+            ScanResult.CHANNEL_WIDTH_20MHZ: RttBW.BW_20_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_40MHZ: RttBW.BW_40_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_80MHZ: RttBW.BW_80_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_160MHZ: RttBW.BW_160_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ: RttBW.BW_160_SUPPORT
+        }
+        p = {}
+        freq = scan_result[RttParam.frequency]
+        p[RttParam.frequency] = freq
+        p[RttParam.BSSID] = scan_result[WifiEnums.BSSID_KEY]
+        if freq > 5000:
+            p[RttParam.preamble] = RttPreamble.PREAMBLE_VHT
+        else:
+            p[RttParam.preamble] = RttPreamble.PREAMBLE_HT
+        cf0 = scan_result[RttParam.center_freq0]
+        if cf0 > 0:
+            p[RttParam.center_freq0] = cf0
+        cf1 = scan_result[RttParam.center_freq1]
+        if cf1 > 0:
+            p[RttParam.center_freq1] = cf1
+        cw = scan_result["channelWidth"]
+        p[RttParam.channel_width] = cw
+        p[RttParam.bandwidth] = scan_result_channel_width_to_rtt[cw]
+        if scan_result["is80211McRTTResponder"]:
+            p[RttParam.request_type] = RttType.TYPE_TWO_SIDED
+        else:
+            p[RttParam.request_type] = RttType.TYPE_ONE_SIDED
+        return p
+
+    """Tests"""
+
+    def test_invalid_params(self):
+        """Tests the check function in RttManager.
+        """
+        param_list = [{
+            RttParam.device_type: 3
+        }, {
+            RttParam.device_type: 1,
+            RttParam.request_type: 3
+        }, {
+            RttParam.device_type: 1,
+            RttParam.request_type: 1,
+            RttParam.BSSID: None
+        }, {
+            RttParam.BSSID: "xxxxxxxx",
+            RttParam.number_burst: 1
+        }, {
+            RttParam.number_burst: 0,
+            RttParam.num_samples_per_burst: -1
+        }, {
+            RttParam.num_samples_per_burst: 32
+        }, {
+            RttParam.num_samples_per_burst: 5,
+            RttParam.num_retries_per_measurement_frame: -1
+        }, {
+            RttParam.num_retries_per_measurement_frame: 4
+        }, {
+            RttParam.num_retries_per_measurement_frame: 2,
+            RttParam.num_retries_per_FTMR: -1
+        }, {
+            RttParam.num_retries_per_FTMR: 4
+        }]
+        for param in param_list:
+            self.invalid_params_logic(param)
+        return True
+
+    def test_support_check(self):
+        """No device supports device-to-device RTT; only shamu and volantis
+        devices support device-to-ap RTT.
+        """
+        model = acts.utils.trim_model_name(self.dut.model)
+        asserts.assert_true(self.dut.droid.wifiIsDeviceToDeviceRttSupported(),
+                            "Device to device is supposed to be supported.")
+        if any([model in m for m in self.support_models]):
+            asserts.assert_true(self.dut.droid.wifiIsDeviceToApRttSupported(),
+                                "%s should support device-to-ap RTT." % model)
+            self.log.info("%s supports device-to-ap RTT as expected." % model)
+        else:
+            asserts.assert_false(
+                self.dut.droid.wifiIsDeviceToApRttSupported(),
+                "%s should not support device-to-ap RTT." % model)
+            self.log.info(
+                ("%s does not support device-to-ap RTT as expected.") % model)
+            asserts.abort_class(
+                "Device %s does not support RTT, abort." % model)
+        return True
+
+    def test_capability_check(self):
+        """Checks the capabilities params are reported as expected.
+        """
+        caps = self.dut.droid.wifiRttGetCapabilities()
+        asserts.assert_true(caps, "Unable to get rtt capabilities.")
+        self.log.debug("Got rtt capabilities %s" % caps)
+        model = acts.utils.trim_model_name(self.dut.model)
+        asserts.assert_true(model in self.rtt_cap_table,
+                            "Unknown model %s" % model)
+        expected_caps = self.rtt_cap_table[model]
+        for k, v in expected_caps.items():
+            asserts.assert_true(k in caps, "%s missing in capabilities." % k)
+            asserts.assert_true(v == caps[k], "Expected %s for %s, got %s." %
+                                (v, k, caps[k]))
+        return True
+
+    def test_discovery(self):
+        """Make sure all the expected 11mc BSSIDs are discovered properly, and
+        they are all reported as 802.11mc Rtt Responder.
+
+        Procedures:
+            1. Scan for wifi networks.
+
+        Expect:
+            All the RTT networks show up in scan results and their
+            "is80211McRTTResponder" is True.
+            All the non-RTT networks show up in scan results and their
+            "is80211McRTTResponder" is False.
+        """
+        wutils.start_wifi_connection_scan(self.dut)
+        scan_results = self.dut.droid.wifiGetScanResults()
+        self.log.debug(scan_results)
+        for n in visible_networks:
+            asserts.assert_true(
+                wutils.match_networks(n, scan_results),
+                "Network %s was not discovered properly." % n)
+        return True
+
+    def test_missing_bssid(self):
+        """Start Rtt ranging with a config that does not have BSSID set.
+        Should not get onSuccess.
+        """
+        p = {}
+        p[RttParam.request_type] = RttType.TYPE_TWO_SIDED
+        p[RttParam.device_type] = RttPeerType.PEER_TYPE_AP
+        p[RttParam.preamble] = RttPreamble.PREAMBLE_VHT
+        p[RttParam.bandwidth] = RttBW.BW_80_SUPPORT
+        p[RttParam.frequency] = self.vht80_5g[WifiEnums.frequency_key]
+        p[RttParam.center_freq0] = self.vht80_5g[RttParam.center_freq0]
+        results = self.get_rtt_results([p])
+        asserts.assert_true(results, "Did not get any result.")
+        self.log.info(pprint.pformat(results))
+
+    def test_rtt_ranging_single_AP_stress(self):
+        """Stress test for Rtt against one AP.
+
+        Steps:
+            1. Do RTT ranging against the self.vht80_5g BSSID.
+            2. Repeat self.stress_num times.
+            3. Verify RTT results.
+        """
+        p = {}
+        p[RttParam.request_type] = RttType.TYPE_TWO_SIDED
+        p[RttParam.device_type] = RttPeerType.PEER_TYPE_AP
+        p[RttParam.preamble] = RttPreamble.PREAMBLE_VHT
+        p[RttParam.bandwidth] = RttBW.BW_80_SUPPORT
+        p[RttParam.BSSID] = self.vht80_5g[WifiEnums.BSSID_KEY]
+        p[RttParam.frequency] = self.vht80_5g[WifiEnums.frequency_key]
+        p[RttParam.center_freq0] = self.vht80_5g[RttParam.center_freq0]
+        p[RttParam.channel_width] = ScanResult.CHANNEL_WIDTH_80MHZ
+        all_results = []
+        for i in range(self.stress_num):
+            self.log.info("RTT Ranging iteration %d" % (i + 1))
+            results = self.get_rtt_results([p])
+            if results:
+                all_results += results
+            else:
+                self.log.warning("Did not get result for iteration %d." % i)
+        frate = self.process_rtt_events(all_results)
+
+    def test_regular_scan_then_rtt_ranging_stress(self):
+        """Stress test for regular scan then start rtt ranging against the RTT
+        compatible networks found by the scan.
+
+        Steps:
+            1. Start a WiFi connection scan.
+            2. Get scan results.
+            3. Find all the 11mc capable BSSIDs and choose the ones to use
+               (self.network_selector)
+            4. Do RTT ranging against the selected BSSIDs, with the info from
+               the scan results.
+            5. Repeat self.stress_num times.
+            6. Verify RTT results.
+        """
+        scan_func = self.regular_scan_for_rtt_networks
+        self.scan_then_rtt_ranging_stress_logic(scan_func)
+
+    def test_gscan_then_rtt_ranging_stress(self):
+        """Stress test for gscan then start rtt ranging against the RTT
+        compatible networks found by the scan.
+
+        Steps:
+            1. Start a WifiScanner single shot scan on all channels.
+            2. Wait for full scan results of the expected 11mc capable BSSIDs.
+            3. Wait for single shot scan to finish on all channels.
+            4. Do RTT ranging against the selected BSSIDs, with the info from
+               the scan results.
+            5. Repeat self.stress_num times.
+            6. Verify RTT results.
+        """
+        scan_func = self.gscan_for_rtt_networks
+        self.scan_then_rtt_ranging_stress_logic(scan_func)
diff --git a/acts_tests/tests/google/wifi/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
new file mode 100644
index 0000000..979ac76
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -0,0 +1,895 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import itertools
+import json
+import logging
+import numpy
+import os
+import time
+from acts import asserts
+from acts import base_test
+from acts import utils
+from acts.controllers import iperf_server as ipf
+from acts.controllers.utils_lib import ssh
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import ota_sniffer
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from functools import partial
+
+
+class WifiRvrTest(base_test.BaseTestClass):
+    """Class to test WiFi rate versus range.
+
+    This class implements WiFi rate versus range tests on single AP single STA
+    links. The class setups up the AP in the desired configurations, configures
+    and connects the phone to the AP, and runs iperf throughput test while
+    sweeping attenuation. For an example config file to run this test class see
+    example_connectivity_performance_ap_sta.json.
+    """
+
+    TEST_TIMEOUT = 6
+    MAX_CONSECUTIVE_ZEROS = 3
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardwares and compiles parameters that are
+        common to all tests in this class.
+        """
+        req_params = [
+            'RetailAccessPoints', 'rvr_test_params', 'testbed_params',
+            'RemoteServer', 'main_network'
+        ]
+        opt_params = ['golden_files_list', 'OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        self.testclass_params = self.rvr_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.iperf_server = self.iperf_servers[0]
+        self.remote_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.iperf_client = self.iperf_clients[0]
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        if not hasattr(self, 'golden_files_list'):
+            if 'golden_results_path' in self.testbed_params:
+                self.golden_files_list = [
+                    os.path.join(self.testbed_params['golden_results_path'],
+                                 file) for file in
+                    os.listdir(self.testbed_params['golden_results_path'])
+                ]
+            else:
+                self.log.warning('No golden files found.')
+                self.golden_files_list = []
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            for dev in self.android_devices:
+                self.log.info('Turning on airplane mode.')
+                asserts.assert_true(utils.force_airplane_mode(dev, True),
+                                    "Can not turn on airplane mode.")
+        wutils.wifi_toggle_state(dev, True)
+
+    def teardown_test(self):
+        self.iperf_server.stop()
+
+    def teardown_class(self):
+        # Turn WiFi OFF
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+        self.process_testclass_results()
+
+    def process_testclass_results(self):
+        """Saves plot with all test results to enable comparison."""
+        # Plot and save all results
+        plots = collections.OrderedDict()
+        for result in self.testclass_results:
+            plot_id = (result['testcase_params']['channel'],
+                       result['testcase_params']['mode'])
+            if plot_id not in plots:
+                plots[plot_id] = wputils.BokehFigure(
+                    title='Channel {} {} ({})'.format(
+                        result['testcase_params']['channel'],
+                        result['testcase_params']['mode'],
+                        result['testcase_params']['traffic_type']),
+                    x_label='Attenuation (dB)',
+                    primary_y_label='Throughput (Mbps)')
+            plots[plot_id].add_line(result['total_attenuation'],
+                                    result['throughput_receive'],
+                                    result['test_name'],
+                                    marker='circle')
+        figure_list = []
+        for plot_id, plot in plots.items():
+            plot.generate_figure()
+            figure_list.append(plot)
+        output_file_path = os.path.join(self.log_path, 'results.html')
+        wputils.BokehFigure.save_figures(figure_list, output_file_path)
+
+    def pass_fail_check(self, rvr_result):
+        """Check the test result and decide if it passed or failed.
+
+        Checks the RvR test result and compares to a throughput limites for
+        the same configuration. The pass/fail tolerances are provided in the
+        config file.
+
+        Args:
+            rvr_result: dict containing attenuation, throughput and other data
+        """
+        try:
+            throughput_limits = self.compute_throughput_limits(rvr_result)
+        except:
+            asserts.explicit_pass(
+                'Test passed by default. Golden file not found')
+
+        failure_count = 0
+        for idx, current_throughput in enumerate(
+                rvr_result['throughput_receive']):
+            if (current_throughput < throughput_limits['lower_limit'][idx]
+                    or current_throughput >
+                    throughput_limits['upper_limit'][idx]):
+                failure_count = failure_count + 1
+
+        # Set test metrics
+        rvr_result['metrics']['failure_count'] = failure_count
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric('failure_count',
+                                                   failure_count)
+
+        # Assert pass or fail
+        if failure_count >= self.testclass_params['failure_count_tolerance']:
+            asserts.fail('Test failed. Found {} points outside limits.'.format(
+                failure_count))
+        asserts.explicit_pass(
+            'Test passed. Found {} points outside throughput limits.'.format(
+                failure_count))
+
+    def compute_throughput_limits(self, rvr_result):
+        """Compute throughput limits for current test.
+
+        Checks the RvR test result and compares to a throughput limites for
+        the same configuration. The pass/fail tolerances are provided in the
+        config file.
+
+        Args:
+            rvr_result: dict containing attenuation, throughput and other meta
+            data
+        Returns:
+            throughput_limits: dict containing attenuation and throughput limit data
+        """
+        test_name = self.current_test_name
+        golden_path = next(file_name for file_name in self.golden_files_list
+                           if test_name in file_name)
+        with open(golden_path, 'r') as golden_file:
+            golden_results = json.load(golden_file)
+            golden_attenuation = [
+                att + golden_results['fixed_attenuation']
+                for att in golden_results['attenuation']
+            ]
+        attenuation = []
+        lower_limit = []
+        upper_limit = []
+        for idx, current_throughput in enumerate(
+                rvr_result['throughput_receive']):
+            current_att = rvr_result['attenuation'][idx] + rvr_result[
+                'fixed_attenuation']
+            att_distances = [
+                abs(current_att - golden_att)
+                for golden_att in golden_attenuation
+            ]
+            sorted_distances = sorted(enumerate(att_distances),
+                                      key=lambda x: x[1])
+            closest_indeces = [dist[0] for dist in sorted_distances[0:3]]
+            closest_throughputs = [
+                golden_results['throughput_receive'][index]
+                for index in closest_indeces
+            ]
+            closest_throughputs.sort()
+
+            attenuation.append(current_att)
+            lower_limit.append(
+                max(
+                    closest_throughputs[0] - max(
+                        self.testclass_params['abs_tolerance'],
+                        closest_throughputs[0] *
+                        self.testclass_params['pct_tolerance'] / 100), 0))
+            upper_limit.append(closest_throughputs[-1] + max(
+                self.testclass_params['abs_tolerance'], closest_throughputs[-1]
+                * self.testclass_params['pct_tolerance'] / 100))
+        throughput_limits = {
+            'attenuation': attenuation,
+            'lower_limit': lower_limit,
+            'upper_limit': upper_limit
+        }
+        return throughput_limits
+
+    def process_test_results(self, rvr_result):
+        """Saves plots and JSON formatted results.
+
+        Args:
+            rvr_result: dict containing attenuation, throughput and other meta
+            data
+        """
+        # Save output as text file
+        test_name = self.current_test_name
+        results_file_path = os.path.join(
+            self.log_path, '{}.json'.format(self.current_test_name))
+        with open(results_file_path, 'w') as results_file:
+            json.dump(rvr_result, results_file, indent=4)
+        # Plot and save
+        figure = wputils.BokehFigure(title=test_name,
+                                     x_label='Attenuation (dB)',
+                                     primary_y_label='Throughput (Mbps)')
+        try:
+            golden_path = next(file_name
+                               for file_name in self.golden_files_list
+                               if test_name in file_name)
+            with open(golden_path, 'r') as golden_file:
+                golden_results = json.load(golden_file)
+            golden_attenuation = [
+                att + golden_results['fixed_attenuation']
+                for att in golden_results['attenuation']
+            ]
+            throughput_limits = self.compute_throughput_limits(rvr_result)
+            shaded_region = {
+                'x_vector': throughput_limits['attenuation'],
+                'lower_limit': throughput_limits['lower_limit'],
+                'upper_limit': throughput_limits['upper_limit']
+            }
+            figure.add_line(golden_attenuation,
+                            golden_results['throughput_receive'],
+                            'Golden Results',
+                            color='green',
+                            marker='circle',
+                            shaded_region=shaded_region)
+        except:
+            self.log.warning('ValueError: Golden file not found')
+
+        # Generate graph annotatios
+        hover_text = [
+            'TX MCS = {0} ({1:.1f}%). RX MCS = {2} ({3:.1f}%)'.format(
+                curr_llstats['summary']['common_tx_mcs'],
+                curr_llstats['summary']['common_tx_mcs_freq'] * 100,
+                curr_llstats['summary']['common_rx_mcs'],
+                curr_llstats['summary']['common_rx_mcs_freq'] * 100)
+            for curr_llstats in rvr_result['llstats']
+        ]
+        figure.add_line(rvr_result['total_attenuation'],
+                        rvr_result['throughput_receive'],
+                        'Test Results',
+                        hover_text=hover_text,
+                        color='red',
+                        marker='circle')
+
+        output_file_path = os.path.join(self.log_path,
+                                        '{}.html'.format(test_name))
+        figure.generate_figure(output_file_path)
+
+        #Set test metrics
+        rvr_result['metrics'] = {}
+        rvr_result['metrics']['peak_tput'] = max(
+            rvr_result['throughput_receive'])
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric(
+                'peak_tput', rvr_result['metrics']['peak_tput'])
+
+        tput_below_limit = [
+            tput < self.testclass_params['tput_metric_targets'][
+                rvr_result['testcase_params']['mode']]['high']
+            for tput in rvr_result['throughput_receive']
+        ]
+        rvr_result['metrics']['high_tput_range'] = -1
+        for idx in range(len(tput_below_limit)):
+            if all(tput_below_limit[idx:]):
+                if idx == 0:
+                    #Throughput was never above limit
+                    rvr_result['metrics']['high_tput_range'] = -1
+                else:
+                    rvr_result['metrics']['high_tput_range'] = rvr_result[
+                        'total_attenuation'][max(idx, 1) - 1]
+                break
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric(
+                'high_tput_range', rvr_result['metrics']['high_tput_range'])
+
+        tput_below_limit = [
+            tput < self.testclass_params['tput_metric_targets'][
+                rvr_result['testcase_params']['mode']]['low']
+            for tput in rvr_result['throughput_receive']
+        ]
+        for idx in range(len(tput_below_limit)):
+            if all(tput_below_limit[idx:]):
+                rvr_result['metrics']['low_tput_range'] = rvr_result[
+                    'total_attenuation'][max(idx, 1) - 1]
+                break
+        else:
+            rvr_result['metrics']['low_tput_range'] = -1
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric(
+                'low_tput_range', rvr_result['metrics']['low_tput_range'])
+
+    def run_rvr_test(self, testcase_params):
+        """Test function to run RvR.
+
+        The function runs an RvR test in the current device/AP configuration.
+        Function is called from another wrapper function that sets up the
+        testbed for the RvR test
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        Returns:
+            rvr_result: dict containing rvr_results and meta data
+        """
+        self.log.info('Start running RvR')
+        # Refresh link layer stats before test
+        llstats_obj = wputils.LinkLayerStats(
+            self.monitored_dut,
+            self.testclass_params.get('monitor_llstats', 1))
+        zero_counter = 0
+        throughput = []
+        llstats = []
+        rssi = []
+        for atten in testcase_params['atten_range']:
+            for dev in self.android_devices:
+                if not wputils.health_check(dev, 5, 50):
+                    asserts.skip('DUT health check failed. Skipping test.')
+            # Set Attenuation
+            for attenuator in self.attenuators:
+                attenuator.set_atten(atten, strict=False)
+            # Refresh link layer stats
+            llstats_obj.update_stats()
+            # Setup sniffer
+            if self.testbed_params['sniffer_enable']:
+                self.sniffer.start_capture(
+                    network=testcase_params['test_network'],
+                    chan=int(testcase_params['channel']),
+                    bw=int(testcase_params['mode'][3:]),
+                    duration=self.testclass_params['iperf_duration'] / 5)
+            # Start iperf session
+            if self.testclass_params.get('monitor_rssi', 1):
+                rssi_future = wputils.get_connected_rssi_nb(
+                    self.monitored_dut,
+                    self.testclass_params['iperf_duration'] - 1,
+                    1,
+                    1,
+                    interface=self.monitored_interface)
+            self.iperf_server.start(tag=str(atten))
+            client_output_path = self.iperf_client.start(
+                testcase_params['iperf_server_address'],
+                testcase_params['iperf_args'], str(atten),
+                self.testclass_params['iperf_duration'] + self.TEST_TIMEOUT)
+            server_output_path = self.iperf_server.stop()
+            if self.testclass_params.get('monitor_rssi', 1):
+                rssi_result = rssi_future.result()
+                current_rssi = {
+                    'signal_poll_rssi':
+                    rssi_result['signal_poll_rssi']['mean'],
+                    'chain_0_rssi': rssi_result['chain_0_rssi']['mean'],
+                    'chain_1_rssi': rssi_result['chain_1_rssi']['mean']
+                }
+            else:
+                current_rssi = {
+                    'signal_poll_rssi': float('nan'),
+                    'chain_0_rssi': float('nan'),
+                    'chain_1_rssi': float('nan')
+                }
+            rssi.append(current_rssi)
+            # Stop sniffer
+            if self.testbed_params['sniffer_enable']:
+                self.sniffer.stop_capture(tag=str(atten))
+            # Parse and log result
+            if testcase_params['use_client_output']:
+                iperf_file = client_output_path
+            else:
+                iperf_file = server_output_path
+            try:
+                iperf_result = ipf.IPerfResult(iperf_file)
+                curr_throughput = numpy.mean(iperf_result.instantaneous_rates[
+                    self.testclass_params['iperf_ignored_interval']:-1]
+                                             ) * 8 * (1.024**2)
+            except:
+                self.log.warning(
+                    'ValueError: Cannot get iperf result. Setting to 0')
+                curr_throughput = 0
+            throughput.append(curr_throughput)
+            llstats_obj.update_stats()
+            curr_llstats = llstats_obj.llstats_incremental.copy()
+            llstats.append(curr_llstats)
+            self.log.info(
+                ('Throughput at {0:.2f} dB is {1:.2f} Mbps. '
+                 'RSSI = {2:.2f} [{3:.2f}, {4:.2f}].').format(
+                     atten, curr_throughput, current_rssi['signal_poll_rssi'],
+                     current_rssi['chain_0_rssi'],
+                     current_rssi['chain_1_rssi']))
+            if curr_throughput == 0 and (
+                    current_rssi['signal_poll_rssi'] < -80
+                    or numpy.isnan(current_rssi['signal_poll_rssi'])):
+                zero_counter = zero_counter + 1
+            else:
+                zero_counter = 0
+            if zero_counter == self.MAX_CONSECUTIVE_ZEROS:
+                self.log.info(
+                    'Throughput stable at 0 Mbps. Stopping test now.')
+                throughput.extend(
+                    [0] *
+                    (len(testcase_params['atten_range']) - len(throughput)))
+                break
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False)
+        # Compile test result and meta data
+        rvr_result = collections.OrderedDict()
+        rvr_result['test_name'] = self.current_test_name
+        rvr_result['testcase_params'] = testcase_params.copy()
+        rvr_result['ap_settings'] = self.access_point.ap_settings.copy()
+        rvr_result['fixed_attenuation'] = self.testbed_params[
+            'fixed_attenuation'][str(testcase_params['channel'])]
+        rvr_result['attenuation'] = list(testcase_params['atten_range'])
+        rvr_result['total_attenuation'] = [
+            att + rvr_result['fixed_attenuation']
+            for att in rvr_result['attenuation']
+        ]
+        rvr_result['rssi'] = rssi
+        rvr_result['throughput_receive'] = throughput
+        rvr_result['llstats'] = llstats
+        return rvr_result
+
+    def setup_ap(self, testcase_params):
+        """Sets up the access point in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '2G' in band:
+            frequency = wutils.WifiEnums.channel_2G_to_freq[
+                testcase_params['channel']]
+        else:
+            frequency = wutils.WifiEnums.channel_5G_to_freq[
+                testcase_params['channel']]
+        if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
+            self.access_point.set_region(self.testbed_params['DFS_region'])
+        else:
+            self.access_point.set_region(self.testbed_params['default_region'])
+        self.access_point.set_channel(band, testcase_params['channel'])
+        self.access_point.set_bandwidth(band, testcase_params['mode'])
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        self.sta_dut = self.android_devices[0]
+        # Check battery level before test
+        if not wputils.health_check(
+                self.sta_dut,
+                20) and testcase_params['traffic_direction'] == 'UL':
+            asserts.skip('Overheating or Battery level low. Skipping test.')
+        # Turn screen off to preserve battery
+        self.sta_dut.go_to_sleep()
+        if wputils.validate_network(self.sta_dut,
+                                    testcase_params['test_network']['SSID']):
+            self.log.info('Already connected to desired network')
+        else:
+            wutils.reset_wifi(self.sta_dut)
+            wutils.set_wifi_country_code(self.sta_dut,
+                                         self.testclass_params['country_code'])
+            if self.testbed_params['sniffer_enable']:
+                self.sniffer.start_capture(
+                    network={'SSID': testcase_params['test_network']['SSID']},
+                    chan=testcase_params['channel'],
+                    bw=testcase_params['mode'][3:],
+                    duration=180)
+            wutils.wifi_connect(self.sta_dut,
+                                testcase_params['test_network'],
+                                num_of_tries=5,
+                                check_connectivity=True)
+            if self.testbed_params['sniffer_enable']:
+                self.sniffer.stop_capture(tag='connection_setup')
+
+    def setup_rvr_test(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Configure AP
+        self.setup_ap(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False)
+        # Reset, configure, and connect DUT
+        self.setup_dut(testcase_params)
+        # Wait before running the first wifi test
+        first_test_delay = self.testclass_params.get('first_test_delay', 600)
+        if first_test_delay > 0 and len(self.testclass_results) == 0:
+            self.log.info('Waiting before the first RvR test.')
+            time.sleep(first_test_delay)
+            self.setup_dut(testcase_params)
+        # Get iperf_server address
+        sta_dut_ip = self.sta_dut.droid.connectivityGetIPv4Addresses(
+            'wlan0')[0]
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            testcase_params['iperf_server_address'] = sta_dut_ip
+        else:
+            testcase_params[
+                'iperf_server_address'] = wputils.get_server_address(
+                    self.remote_server, sta_dut_ip, '255.255.255.0')
+        # Set DUT to monitor RSSI and LLStats on
+        self.monitored_dut = self.sta_dut
+        self.monitored_interface = None
+
+    def compile_test_params(self, testcase_params):
+        """Function that completes all test params based on the test name.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        num_atten_steps = int((self.testclass_params['atten_stop'] -
+                               self.testclass_params['atten_start']) /
+                              self.testclass_params['atten_step'])
+        testcase_params['atten_range'] = [
+            self.testclass_params['atten_start'] +
+            x * self.testclass_params['atten_step']
+            for x in range(0, num_atten_steps)
+        ]
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[band]
+        if testcase_params['traffic_type'] == 'TCP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'tcp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'tcp_processes', 1)
+        elif testcase_params['traffic_type'] == 'UDP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'udp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'udp_processes', 1)
+        if (testcase_params['traffic_direction'] == 'DL'
+                and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
+            ) or (testcase_params['traffic_direction'] == 'UL'
+                  and isinstance(self.iperf_server, ipf.IPerfServerOverAdb)):
+            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+                duration=self.testclass_params['iperf_duration'],
+                reverse_direction=1,
+                traffic_type=testcase_params['traffic_type'],
+                socket_size=testcase_params['iperf_socket_size'],
+                num_processes=testcase_params['iperf_processes'])
+            testcase_params['use_client_output'] = True
+        else:
+            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+                duration=self.testclass_params['iperf_duration'],
+                reverse_direction=0,
+                traffic_type=testcase_params['traffic_type'],
+                socket_size=testcase_params['iperf_socket_size'],
+                num_processes=testcase_params['iperf_processes'])
+            testcase_params['use_client_output'] = False
+        return testcase_params
+
+    def _test_rvr(self, testcase_params):
+        """ Function that gets called for each test case
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+
+        # Prepare devices and run test
+        self.setup_rvr_test(testcase_params)
+        rvr_result = self.run_rvr_test(testcase_params)
+
+        # Post-process results
+        self.testclass_results.append(rvr_result)
+        self.process_test_results(rvr_result)
+        self.pass_fail_check(rvr_result)
+
+    def generate_test_cases(self, channels, modes, traffic_types,
+                            traffic_directions):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 64, 100,
+                116, 132, 140, 149, 153, 157, 161
+            ],
+            'VHT40': [36, 44, 100, 149, 157],
+            'VHT80': [36, 100, 149]
+        }
+
+        for channel, mode, traffic_type, traffic_direction in itertools.product(
+                channels, modes, traffic_types, traffic_directions):
+            if channel not in allowed_configs[mode]:
+                continue
+            test_name = 'test_rvr_{}_{}_ch{}_{}'.format(
+                traffic_type, traffic_direction, channel, mode)
+            test_params = collections.OrderedDict(
+                channel=channel,
+                mode=mode,
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction)
+            setattr(self, test_name, partial(self._test_rvr, test_params))
+            test_cases.append(test_name)
+        return test_cases
+
+
+# Classes defining test suites
+class WifiRvr_2GHz_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(channels=[1, 6, 11],
+                                              modes=['VHT20'],
+                                              traffic_types=['TCP'],
+                                              traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_UNII1_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[36, 40, 44, 48],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_UNII3_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_SampleDFS_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[64, 100, 116, 132, 140],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_SampleUDP_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[6, 36, 149],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['UDP'],
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_TCP_All_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_TCP_Downlink_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL'])
+
+
+class WifiRvr_TCP_Uplink_Test(WifiRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['VHT20', 'VHT40', 'VHT80'],
+            traffic_types=['TCP'],
+            traffic_directions=['UL'])
+
+
+# Over-the air version of RVR tests
+class WifiOtaRvrTest(WifiRvrTest):
+    """Class to test over-the-air RvR
+
+    This class implements measures WiFi RvR tests in an OTA chamber. It enables
+    setting turntable orientation and other chamber parameters to study
+    performance in varying channel conditions
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = False
+
+    def setup_class(self):
+        WifiRvrTest.setup_class(self)
+        self.ota_chamber = ota_chamber.create(
+            self.user_params['OTAChamber'])[0]
+
+    def teardown_class(self):
+        WifiRvrTest.teardown_class(self)
+        self.ota_chamber.reset_chamber()
+
+    def extract_test_id(self, testcase_params, id_fields):
+        test_id = collections.OrderedDict(
+            (param, testcase_params[param]) for param in id_fields)
+        return test_id
+
+    def process_testclass_results(self):
+        """Saves plot with all test results to enable comparison."""
+        # Plot individual test id results raw data and compile metrics
+        plots = collections.OrderedDict()
+        compiled_data = collections.OrderedDict()
+        for result in self.testclass_results:
+            test_id = tuple(
+                self.extract_test_id(
+                    result['testcase_params'],
+                    ['channel', 'mode', 'traffic_type', 'traffic_direction'
+                     ]).items())
+            if test_id not in plots:
+                # Initialize test id data when not present
+                compiled_data[test_id] = {'throughput': [], 'metrics': {}}
+                compiled_data[test_id]['metrics'] = {
+                    key: []
+                    for key in result['metrics'].keys()
+                }
+                plots[test_id] = wputils.BokehFigure(
+                    title='Channel {} {} ({} {})'.format(
+                        result['testcase_params']['channel'],
+                        result['testcase_params']['mode'],
+                        result['testcase_params']['traffic_type'],
+                        result['testcase_params']['traffic_direction']),
+                    x_label='Attenuation (dB)',
+                    primary_y_label='Throughput (Mbps)')
+            # Compile test id data and metrics
+            compiled_data[test_id]['throughput'].append(
+                result['throughput_receive'])
+            compiled_data[test_id]['total_attenuation'] = result[
+                'total_attenuation']
+            for metric_key, metric_value in result['metrics'].items():
+                compiled_data[test_id]['metrics'][metric_key].append(
+                    metric_value)
+            # Add test id to plots
+            plots[test_id].add_line(result['total_attenuation'],
+                                    result['throughput_receive'],
+                                    result['test_name'],
+                                    width=1,
+                                    style='dashed',
+                                    marker='circle')
+
+        # Compute average RvRs and compount metrics over orientations
+        for test_id, test_data in compiled_data.items():
+            test_id_dict = dict(test_id)
+            metric_tag = '{}_{}_ch{}_{}'.format(
+                test_id_dict['traffic_type'],
+                test_id_dict['traffic_direction'], test_id_dict['channel'],
+                test_id_dict['mode'])
+            high_tput_hit_freq = numpy.mean(
+                numpy.not_equal(test_data['metrics']['high_tput_range'], -1))
+            self.testclass_metric_logger.add_metric(
+                '{}.high_tput_hit_freq'.format(metric_tag), high_tput_hit_freq)
+            for metric_key, metric_value in test_data['metrics'].items():
+                metric_key = "{}.avg_{}".format(metric_tag, metric_key)
+                metric_value = numpy.mean(metric_value)
+                self.testclass_metric_logger.add_metric(
+                    metric_key, metric_value)
+            test_data['avg_rvr'] = numpy.mean(test_data['throughput'], 0)
+            test_data['median_rvr'] = numpy.median(test_data['throughput'], 0)
+            plots[test_id].add_line(test_data['total_attenuation'],
+                                    test_data['avg_rvr'],
+                                    legend='Average Throughput',
+                                    marker='circle')
+            plots[test_id].add_line(test_data['total_attenuation'],
+                                    test_data['median_rvr'],
+                                    legend='Median Throughput',
+                                    marker='square')
+
+        figure_list = []
+        for test_id, plot in plots.items():
+            plot.generate_figure()
+            figure_list.append(plot)
+        output_file_path = os.path.join(self.log_path, 'results.html')
+        wputils.BokehFigure.save_figures(figure_list, output_file_path)
+
+    def setup_rvr_test(self, testcase_params):
+        # Set turntable orientation
+        self.ota_chamber.set_orientation(testcase_params['orientation'])
+        # Continue test setup
+        WifiRvrTest.setup_rvr_test(self, testcase_params)
+
+    def generate_test_cases(self, channels, modes, angles, traffic_types,
+                            directions):
+        test_cases = []
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+        for channel, mode, angle, traffic_type, direction in itertools.product(
+                channels, modes, angles, traffic_types, directions):
+            if channel not in allowed_configs[mode]:
+                continue
+            testcase_name = 'test_rvr_{}_{}_ch{}_{}_{}deg'.format(
+                traffic_type, direction, channel, mode, angle)
+            test_params = collections.OrderedDict(channel=channel,
+                                                  mode=mode,
+                                                  traffic_type=traffic_type,
+                                                  traffic_direction=direction,
+                                                  orientation=angle)
+            setattr(self, testcase_name, partial(self._test_rvr, test_params))
+            test_cases.append(testcase_name)
+        return test_cases
+
+
+class WifiOtaRvr_StandardOrientation_Test(WifiOtaRvrTest):
+    def __init__(self, controllers):
+        WifiOtaRvrTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(
+            [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            ['VHT20', 'VHT40', 'VHT80'], list(range(0, 360,
+                                                    45)), ['TCP'], ['DL'])
+
+
+class WifiOtaRvr_SampleChannel_Test(WifiOtaRvrTest):
+    def __init__(self, controllers):
+        WifiOtaRvrTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases([6], ['VHT20'],
+                                              list(range(0, 360, 45)), ['TCP'],
+                                              ['DL'])
+        self.tests.extend(
+            self.generate_test_cases([36, 149], ['VHT80'],
+                                     list(range(0, 360, 45)), ['TCP'], ['DL']))
+
+
+class WifiOtaRvr_SingleOrientation_Test(WifiOtaRvrTest):
+    def __init__(self, controllers):
+        WifiOtaRvrTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases(
+            [6, 36, 40, 44, 48, 149, 153, 157, 161],
+            ['VHT20', 'VHT40', 'VHT80'], [0], ['TCP'], ['DL', 'UL'])
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiRvrTwTest.py b/acts_tests/tests/google/wifi/WifiRvrTwTest.py
new file mode 100644
index 0000000..2f9dc12
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiRvrTwTest.py
@@ -0,0 +1,480 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import time
+
+import acts.signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.controllers import iperf_server as ipf
+
+import json
+import logging
+import math
+import os
+from acts import utils
+import csv
+
+import serial
+import sys
+
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiRvrTWTest(WifiBaseTest):
+    """ Tests for wifi RVR performance
+
+        Test Bed Requirement:
+          * One Android device
+          * Wi-Fi networks visible to the device
+    """
+    TEST_TIMEOUT = 10
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+
+        req_params = [ "iot_networks","rvr_test_params"]
+        opt_params = [ "angle_params","usb_port"]
+        self.unpack_userparams(req_param_names=req_params,
+                               opt_param_names=opt_params)
+
+        asserts.assert_true(
+            len(self.iot_networks) > 0,
+            "Need at least one iot network with psk.")
+
+        wutils.wifi_toggle_state(self.dut, True)
+        if "rvr_test_params" in self.user_params:
+            self.iperf_server = self.iperf_servers[0]
+            self.MaxdB= self.rvr_test_params ["rvr_atten_MaxDB"]
+            self.MindB= self.rvr_test_params ["rvr_atten_MinDB"]
+            self.stepdB= self.rvr_test_params ["rvr_atten_step"]
+
+        if "angle_params" in self.user_params:
+            self.angle = self.angle_params
+
+        if "usb_port" in self.user_params:
+            self.T1=self.readport(self.usb_port["turntable"])
+            self.ATT1=self.readport(self.usb_port["atten1"])
+            self.ATT2=self.readport(self.usb_port["atten2"])
+            self.ATT3=self.readport(self.usb_port["atten3"])
+
+        # create hashmap for testcase name and SSIDs
+        self.iot_test_prefix = "test_iot_connection_to_"
+        self.ssid_map = {}
+        for network in self.iot_networks:
+            SSID = network['SSID'].replace('-','_')
+            self.ssid_map[SSID] = network
+
+        # create folder for rvr test result
+        self.log_path = os.path.join(logging.log_path, "rvr_results")
+        os.makedirs(self.log_path, exist_ok=True)
+
+        Header=("test_SSID","Turn table (angle)","Attenuator(dBm)",
+                "TX throughput (Mbps)","RX throughput (Mbps)",
+                "RSSI","Link speed","Frequency")
+        self.csv_write(Header)
+
+    def setup_test(self):
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+
+    def teardown_class(self):
+        if "rvr_test_params" in self.user_params:
+            self.iperf_server.stop()
+
+    def on_fail(self, test_name, begin_time):
+        self.dut.take_bug_report(test_name, begin_time)
+        self.dut.cat_adb_log(test_name, begin_time)
+
+    """Helper Functions"""
+
+    def csv_write(self,data):
+        """Output .CSV file for test result.
+
+        Args:
+            data: Dict containing attenuation, throughput and other meta data.
+        """
+        with open("{}/Result.csv".format(self.log_path), "a", newline="") as csv_file:
+            csv_writer = csv.writer(csv_file,delimiter=',')
+            csv_writer.writerow(data)
+            csv_file.close()
+
+    def readport(self,com):
+        """Read com port for current test.
+
+        Args:
+            com: Attenuator or turn table com port
+        """
+        port=serial.Serial(com,9600,timeout=1)
+        time.sleep(1)
+        return port
+
+    def getdB(self,port):
+        """Get attenuator dB for current test.
+
+        Args:
+            port: Attenuator com port
+        """
+        port.write('V?;'.encode())
+        dB=port.readline().decode()
+        dB=dB.strip(';')
+        dB=dB[dB.find('V')+1:]
+        return int(dB)
+
+    def setdB(self,port,dB):
+        """Setup attenuator dB for current test.
+
+        Args:
+            port: Attenuator com port
+            dB: Attenuator setup dB
+        """
+        if dB<0:
+            dB=0
+        elif dB>101:
+            dB=101
+        self.log.info("Set dB to "+str(dB))
+        InputdB=str('V')+str(dB)+str(';')
+        port.write(InputdB.encode())
+        time.sleep(0.1)
+
+    def set_Three_Att_dB(self,port1,port2,port3,dB):
+        """Setup 3 attenuator dB for current test.
+
+        Args:
+            port1: Attenuator1 com port
+            port1: Attenuator2 com port
+            port1: Attenuator com port
+            dB: Attenuator setup dB
+        """
+        self.setdB(port1,dB)
+        self.setdB(port2,dB)
+        self.setdB(port3,dB)
+        self.checkdB(port1,dB)
+        self.checkdB(port2,dB)
+        self.checkdB(port3,dB)
+
+    def checkdB(self,port,dB):
+        """Check attenuator dB for current test.
+
+        Args:
+            port: Attenuator com port
+            dB: Attenuator setup dB
+        """
+        retry=0
+        while self.getdB(port)!=dB and retry<10:
+            retry=retry+1
+            self.log.info("Current dB = "+str(self.getdB(port)))
+            self.log.info("Fail to set Attenuator to "+str(dB)+", "
+                          +str(retry)+" times try to reset")
+            self.setdB(port,dB)
+        if retry ==10:
+            self.log.info("Retry Attenuator fail for 9 cycles, end test!")
+            sys.exit()
+        return 0
+
+    def getDG(self,port):
+        """Get turn table angle for current test.
+
+        Args:
+            port: Turn table com port
+        """
+        DG = ""
+        port.write('DG?;'.encode())
+        time.sleep(0.1)
+        data = port.readline().decode('utf-8')
+        for i in range(len(data)):
+            if (data[i].isdigit()) == True:
+                DG = DG + data[i]
+        if DG == "":
+            return -1
+        return int(DG)
+
+    def setDG(self,port,DG):
+        """Setup turn table angle for current test.
+
+        Args:
+            port: Turn table com port
+            DG: Turn table setup angle
+        """
+        if DG>359:
+            DG=359
+        elif DG<0:
+            DG=0
+        self.log.info("Set angle to "+str(DG))
+        InputDG=str('DG')+str(DG)+str(';')
+        port.write(InputDG.encode())
+
+    def checkDG(self,port,DG):
+        """Check turn table angle for current test.
+
+        Args:
+            port: Turn table com port
+            DG: Turn table setup angle
+        """
+        retrytime = self.TEST_TIMEOUT
+        retry = 0
+        while self.getDG(port)!=DG and retry<retrytime:
+            retry=retry+1
+            self.log.info('Current angle = '+str(self.getDG(port)))
+            self.log.info('Fail set angle to '+str(DG)+', '+str(retry)+' times try to reset')
+            self.setDG(port,DG)
+            time.sleep(10)
+        if retry == retrytime:
+            self.log.info('Retry turntable fail for '+str(retry)+' cycles, end test!')
+            sys.exit()
+        return 0
+
+    def getwifiinfo(self):
+        """Get WiFi RSSI/ link speed/ frequency for current test.
+
+        Returns:
+            [RSSI,LS,FR]: WiFi RSSI/ link speed/ frequency
+        """
+        def is_number(string):
+            for i in string:
+                if i.isdigit() == False:
+                    if (i=="-" or i=="."):
+                        continue
+                    return str(-1)
+            return string
+
+        try:
+            cmd = "adb shell iw wlan0 link"
+            wifiinfo = utils.subprocess.check_output(cmd,shell=True,
+                                                     timeout=self.TEST_TIMEOUT)
+            # Check RSSI
+            RSSI = wifiinfo.decode("utf-8")[wifiinfo.decode("utf-8").find("signal:") +
+                                            7:wifiinfo.decode("utf-8").find("dBm") - 1]
+            RSSI = RSSI.strip(' ')
+            RSSI = is_number(RSSI)
+            # Check link speed
+            LS = wifiinfo.decode("utf-8")[wifiinfo.decode("utf-8").find("bitrate:") +
+                                          8:wifiinfo.decode("utf-8").find("Bit/s") - 2]
+            LS = LS.strip(' ')
+            LS = is_number(LS)
+            # Check frequency
+            FR = wifiinfo.decode("utf-8")[wifiinfo.decode("utf-8").find("freq:") +
+                                          6:wifiinfo.decode("utf-8").find("freq:") + 10]
+            FR = FR.strip(' ')
+            FR = is_number(FR)
+        except:
+            return -1, -1, -1
+        return [RSSI,LS,FR]
+
+    def post_process_results(self, rvr_result):
+        """Saves JSON formatted results.
+
+        Args:
+            rvr_result: Dict containing attenuation, throughput and other meta
+            data
+        """
+        # Save output as text file
+        data=(rvr_result["test_name"],rvr_result["test_angle"],rvr_result["test_dB"],
+              rvr_result["throughput_TX"][0],rvr_result["throughput_RX"][0],
+              rvr_result["test_RSSI"],rvr_result["test_LS"],rvr_result["test_FR"])
+        self.csv_write(data)
+
+        results_file_path = "{}/{}_angle{}_{}dB.json".format(self.log_path,
+                                                        self.ssid,
+                                                        self.angle[self.ag],self.DB)
+        with open(results_file_path, 'w') as results_file:
+            json.dump(rvr_result, results_file, indent=4)
+
+    def connect_to_wifi_network(self, network):
+        """Connection logic for psk wifi networks.
+
+        Args:
+            params: Dictionary with network info.
+        """
+        SSID = network[WifiEnums.SSID_KEY]
+        self.dut.ed.clear_all_events()
+        wutils.start_wifi_connection_scan(self.dut)
+        scan_results = self.dut.droid.wifiGetScanResults()
+        wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
+        wutils.wifi_connect(self.dut, network, num_of_tries=3)
+
+    def run_iperf_client(self, network):
+        """Run iperf TX throughput after connection.
+
+        Args:
+            params: Dictionary with network info.
+
+        Returns:
+            rvr_result: Dict containing rvr_results
+        """
+        rvr_result = []
+        self.iperf_server.start(tag="TX_server_{}_angle{}_{}dB".format(
+            self.ssid,self.angle[self.ag],self.DB))
+        wait_time = 5
+        SSID = network[WifiEnums.SSID_KEY]
+        self.log.info("Starting iperf traffic TX through {}".format(SSID))
+        time.sleep(wait_time)
+        port_arg = "-p {} -J {}".format(self.iperf_server.port,
+                                        self.rvr_test_params["iperf_port_arg"])
+        success, data = self.dut.run_iperf_client(
+            self.rvr_test_params["iperf_server_address"],
+            port_arg,
+            timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
+        # Parse and log result
+        client_output_path = os.path.join(
+            self.iperf_server.log_path, "IperfDUT,{},TX_client_{}_angle{}_{}dB".format(
+                self.iperf_server.port,self.ssid,self.angle[self.ag],self.DB))
+        with open(client_output_path, 'w') as out_file:
+            out_file.write("\n".join(data))
+        self.iperf_server.stop()
+
+        iperf_file = self.iperf_server.log_files[-1]
+        try:
+            iperf_result = ipf.IPerfResult(iperf_file)
+            curr_throughput = (math.fsum(iperf_result.instantaneous_rates[
+                self.rvr_test_params["iperf_ignored_interval"]:-1]) / len(
+                    iperf_result.instantaneous_rates[self.rvr_test_params[
+                        "iperf_ignored_interval"]:-1])) * 8 * (1.024**2)
+        except:
+            self.log.warning(
+                "ValueError: Cannot get iperf result. Setting to 0")
+            curr_throughput = 0
+        rvr_result.append(curr_throughput)
+        self.log.info("TX Throughput at {0:.2f} dB is {1:.2f} Mbps".format(
+            self.DB, curr_throughput))
+
+        self.log.debug(pprint.pformat(data))
+        asserts.assert_true(success, "Error occurred in iPerf traffic.")
+        return rvr_result
+
+    def run_iperf_server(self, network):
+        """Run iperf RX throughput after connection.
+
+        Args:
+            params: Dictionary with network info.
+
+        Returns:
+            rvr_result: Dict containing rvr_results
+        """
+        rvr_result = []
+        self.iperf_server.start(tag="RX_client_{}_angle{}_{}dB".format(
+            self.ssid,self.angle[self.ag],self.DB))
+        wait_time = 5
+        SSID = network[WifiEnums.SSID_KEY]
+        self.log.info("Starting iperf traffic RX through {}".format(SSID))
+        time.sleep(wait_time)
+        port_arg = "-p {} -J -R {}".format(self.iperf_server.port,
+                                           self.rvr_test_params["iperf_port_arg"])
+        success, data = self.dut.run_iperf_client(
+            self.rvr_test_params["iperf_server_address"],
+            port_arg,
+            timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
+        # Parse and log result
+        client_output_path = os.path.join(
+        self.iperf_server.log_path, "IperfDUT,{},RX_server_{}_angle{}_{}dB".format(
+            self.iperf_server.port,self.ssid,self.angle[self.ag],self.DB))
+        with open(client_output_path, 'w') as out_file:
+            out_file.write("\n".join(data))
+        self.iperf_server.stop()
+
+        iperf_file = client_output_path
+        try:
+            iperf_result = ipf.IPerfResult(iperf_file)
+            curr_throughput = (math.fsum(iperf_result.instantaneous_rates[
+                self.rvr_test_params["iperf_ignored_interval"]:-1]) / len(
+                    iperf_result.instantaneous_rates[self.rvr_test_params[
+                        "iperf_ignored_interval"]:-1])) * 8 * (1.024**2)
+        except:
+            self.log.warning(
+                "ValueError: Cannot get iperf result. Setting to 0")
+            curr_throughput = 0
+        rvr_result.append(curr_throughput)
+        self.log.info("RX Throughput at {0:.2f} dB is {1:.2f} Mbps".format(
+            self.DB, curr_throughput))
+
+        self.log.debug(pprint.pformat(data))
+        asserts.assert_true(success, "Error occurred in iPerf traffic.")
+        return rvr_result
+
+    def iperf_test_func(self,network):
+        """Main function to test iperf TX/RX.
+
+        Args:
+            params: Dictionary with network info
+        """
+        if "rvr_test_params" in self.user_params:
+            # Initialize
+            rvr_result = {}
+            # Run RvR and log result
+            wifiinfo = self.getwifiinfo()
+            rvr_result["throughput_TX"] = self.run_iperf_client(network)
+            rvr_result["throughput_RX"] = self.run_iperf_server(network)
+            rvr_result["test_name"] = self.ssid
+            rvr_result["test_angle"] = self.angle[self.ag]
+            rvr_result["test_dB"] = self.DB
+            rvr_result["test_RSSI"] = wifiinfo[0]
+            rvr_result["test_LS"] = wifiinfo[1]
+            rvr_result["test_FR"] = wifiinfo[2]
+            self.post_process_results(rvr_result)
+
+    def rvr_test(self,network):
+        """Test function to run RvR.
+
+        The function runs an RvR test in the current device/AP configuration.
+        Function is called from another wrapper function that sets up the
+        testbed for the RvR test
+
+        Args:
+            params: Dictionary with network info
+        """
+        wait_time = 5
+        utils.subprocess.check_output('adb root', shell=True, timeout=20)
+        self.ssid = network[WifiEnums.SSID_KEY]
+        self.log.info("Start rvr test")
+        for i in range(len(self.angle)):
+          self.setDG(self.T1,self.angle[i])
+          time.sleep(wait_time)
+          self.checkDG(self.T1,self.angle[i])
+          self.set_Three_Att_dB(self.ATT1,self.ATT2,self.ATT3,0)
+          time.sleep(wait_time)
+          self.connect_to_wifi_network(network)
+          self.set_Three_Att_dB(self.ATT1,self.ATT2,self.ATT3,self.MindB)
+          for j in range(self.MindB,self.MaxdB+self.stepdB,self.stepdB):
+            self.DB=j
+            self.ag=i
+            self.set_Three_Att_dB(self.ATT1,self.ATT2,self.ATT3,self.DB)
+            self.iperf_test_func(network)
+          wutils.reset_wifi(self.dut)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="93816af8-4c63-45f8-b296-cb49fae0b158")
+    def test_iot_connection_to_RVR_2G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.rvr_test(self.ssid_map[ssid_key])
+
+    @test_tracker_info(uuid="e1a67e13-946f-4d91-aa73-3f945438a1ac")
+    def test_iot_connection_to_RVR_5G(self):
+        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
+        self.rvr_test(self.ssid_map[ssid_key])
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiScannerBssidTest.py b/acts_tests/tests/google/wifi/WifiScannerBssidTest.py
new file mode 100644
index 0000000..e91c449
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiScannerBssidTest.py
@@ -0,0 +1,482 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import queue
+
+from acts import asserts
+from acts import base_test
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+
+BSSID_EVENT_WAIT = 30
+
+BSSID_EVENT_TAG = "WifiScannerBssid"
+SCAN_EVENT_TAG = "WifiScannerScan"
+SCANTIME = 10000  #framework support only 10s as minimum scan interval
+
+
+class WifiScannerBssidError(Exception):
+    pass
+
+
+class WifiScannerBssidTest(base_test.BaseTestClass):
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        # A list of all test cases to be executed in this class.
+        self.tests = ("test_wifi_track_bssid_sanity",
+                      "test_wifi_track_bssid_found",
+                      "test_wifi_track_bssid_lost",
+                      "test_wifi_track_bssid_for_2g_while_scanning_5g_channels",
+                      "test_wifi_track_bssid_for_5g_while_scanning_2g_channels",)
+
+    def setup_class(self):
+        self.default_scan_setting = {
+            "band": wutils.WifiEnums.WIFI_BAND_BOTH_WITH_DFS,
+            "periodInMs": SCANTIME,
+            "reportEvents": wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+            'numBssidsPerScan': 32
+        }
+        self.leeway = 5
+        self.stime_channel = 47  #dwell time plus 2ms
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        self.attenuators = wutils.group_attenuators(self.attenuators)
+        asserts.assert_true(self.dut.droid.wifiIsScannerSupported(),
+                            "Device %s doesn't support WifiScanner, abort." %
+                            self.dut.model)
+        """It will setup the required dependencies and fetch the user params from
+          config file"""
+        self.attenuators[0].set_atten(0)
+        self.attenuators[1].set_atten(0)
+        req_params = ("bssid_2g", "bssid_5g", "bssid_dfs", "attenuator_id",
+                      "max_bugreports")
+        self.wifi_chs = wutils.WifiChannelUS(self.dut.model)
+        self.unpack_userparams(req_params, two_ap_testbed=False)
+
+    def teardown_class(self):
+        BaseTestClass.teardown_test(self)
+        self.log.debug("Shut down all wifi scanner activities.")
+        self.dut.droid.wifiScannerShutdown()
+
+    def on_fail(self, test_name, begin_time):
+        if self.max_bugreports > 0:
+            self.dut.take_bug_report(test_name, begin_time)
+            self.max_bugreports -= 1
+
+    """ Helper Functions Begin """
+
+    def fetch_scan_result(self, scan_idx, scan_setting):
+        """Fetch the scan result for provider listener index.
+
+        This function calculate the time required for scanning based on scan setting
+        and wait for scan result event, on triggering of event process the scan result.
+
+        Args:
+          scan_idx: Index of the scan listener.
+          scan_setting: Setting used for starting the scan.
+
+        Returns:
+          scan_results: if scan result available.
+        """
+        #generating event wait time from scan setting plus leeway
+        self.log.debug(scan_setting)
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        scan_time += scan_setting['periodInMs'
+                                  ]  #add scan period delay for next cycle
+        if scan_setting[
+                "reportEvents"] == wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN:
+            waittime = int(scan_time / 1000) + self.leeway
+        else:
+            time_cache = scan_setting['periodInMs'] * 10  #default cache
+            waittime = int((time_cache + scan_time) / 1000) + self.leeway
+        event_name = "%s%sonResults" % (SCAN_EVENT_TAG, scan_idx)
+        self.log.info("Waiting for the scan result event %s", event_name)
+        event = self.dut.ed.pop_event(event_name, waittime)
+        results = event["data"]["Results"]
+        if len(results) > 0 and "ScanResults" in results[0]:
+            return results[0]["ScanResults"]
+
+    def start_scan_and_validate_environment(self, scan_setting,
+                                            bssid_settings):
+        """Validate environment for test using current scan result for provided
+           settings.
+
+        This function start the scan for given setting and verify that interested
+        Bssids are in scan result or not.
+
+        Args:
+            scan_setting: Setting used for starting the scan.
+            bssid_settings: list of bssid settings.
+
+        Returns:
+            True, if bssid not found in scan result.
+        """
+        try:
+            data = wutils.start_wifi_background_scan(self.dut, scan_setting)
+            self.scan_idx = data["Index"]
+            results = self.fetch_scan_result(self.scan_idx, scan_setting)
+            self.log.debug("scan result %s.", results)
+            asserts.assert_true(results,
+                                "Device is not able to fetch the scan results")
+            for result in results:
+                for bssid_setting in bssid_settings:
+                    if bssid_setting[wutils.WifiEnums.BSSID_KEY] == result[
+                            wutils.WifiEnums.BSSID_KEY]:
+                        asserts.fail(("Test environment is not valid: Bssid %s"
+                                      "already exist in current scan results")
+                                     % result[wutils.WifiEnums.BSSID_KEY])
+        except queue.Empty as error:
+            self.dut.droid.wifiScannerStopBackgroundScan(self.scan_idx)
+            raise AssertionError(
+                "OnResult event did not triggered for scanner\n%s" % error)
+
+    def check_bssid_in_found_result(self, bssid_settings, found_results):
+        """look for any tracked bssid in reported result of found bssids.
+
+        Args:
+            bssid_settings:Setting used for tracking bssids.
+            found_results: Result reported in found event.
+
+        Returns:
+            True if bssid is present in result.
+        """
+        for bssid_setting in bssid_settings:
+            for found_result in found_results:
+                if found_result[wutils.WifiEnums.BSSID_KEY] == bssid_setting[
+                        wutils.WifiEnums.BSSID_KEY]:
+                    return
+        asserts.fail("Test fail because Bssid %s is not found in event results"
+                     % bssid_settings)
+
+    def track_bssid_with_vaild_scan_for_found(self, track_setting):
+        """Common logic for tracking a bssid for Found event.
+
+         1. Starts Wifi Scanner bssid tracking for interested bssids in track_setting.
+         2. Start Wifi Scanner scan with default scan settings.
+         3. Validate the environment to check AP is not in range.
+         4. Attenuate the signal to make AP in range.
+         5. Verified that onFound event is triggered for interested bssids in
+            track setting.
+
+        Args:
+            track_setting: Setting for bssid tracking.
+
+        Returns:
+            True if found event occur for interested BSSID.
+        """
+        self.attenuators[self.attenuator_id].set_atten(90)
+        data = wutils.start_wifi_track_bssid(self.dut, track_setting)
+        idx = data["Index"]
+        self.start_scan_and_validate_environment(self.default_scan_setting,
+                                                 track_setting["bssidInfos"])
+        try:
+            self.attenuators[self.attenuator_id].set_atten(0)
+            event_name = "%s%sonFound" % (BSSID_EVENT_TAG, idx)
+            self.log.info("Waiting for the BSSID event %s", event_name)
+            event = self.dut.ed.pop_event(event_name, BSSID_EVENT_WAIT)
+            self.log.debug(event)
+            self.check_bssid_in_found_result(track_setting["bssidInfos"],
+                                             event["data"]["Results"])
+        except queue.Empty as error:
+            self.log.error(error)
+            # log scan result for debugging
+            results = self.fetch_scan_result(self.scan_idx,
+                                             self.default_scan_setting)
+            self.log.debug("scan result %s", results)
+            raise AssertionError("Event %s did not triggered for %s\n%s" %
+                                 (event_name, track_setting["bssidInfos"],
+                                  error))
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(self.scan_idx)
+            self.dut.droid.wifiScannerStopTrackingBssids(idx)
+
+    def track_bssid_with_vaild_scan_for_lost(self, track_setting):
+        """Common logic for tracking a bssid for Lost event.
+
+         1. Start Wifi Scanner scan with default scan settings.
+         2. Validate the environment to check AP is not in range.
+         3. Starts Wifi Scanner bssid tracking for interested bssids in track_setting.
+         4. Attenuate the signal to make Bssids in range.
+         5. Verified that onFound event is triggered for interested bssids in
+            track setting.
+         6. Attenuate the signal to make Bssids out of range.
+         7. Verified that onLost event is triggered.
+
+        Args:
+            track_setting: Setting for bssid tracking.
+            scan_setting: Setting used for starting the scan.
+
+        Returns:
+            True if Lost event occur for interested BSSID.
+        """
+        self.attenuators[self.attenuator_id].set_atten(90)
+        self.start_scan_and_validate_environment(self.default_scan_setting,
+                                                 track_setting["bssidInfos"])
+        idx = None
+        found = False
+        try:
+            data = wutils.start_wifi_track_bssid(self.dut, track_setting)
+            idx = data["Index"]
+            self.attenuators[self.attenuator_id].set_atten(0)
+            #onFound event should be occurre before tracking for onLost event
+            event_name = "%s%sonFound" % (BSSID_EVENT_TAG, idx)
+            self.log.info("Waiting for the BSSID event %s", event_name)
+            event = self.dut.ed.pop_event(event_name, BSSID_EVENT_WAIT)
+            self.log.debug(event)
+            self.check_bssid_in_found_result(track_setting["bssidInfos"],
+                                             event["data"]["Results"])
+            self.attenuators[self.attenuator_id].set_atten(90)
+            # log scan result for debugging
+            for i in range(1, track_setting["apLostThreshold"]):
+                results = self.fetch_scan_result(self.scan_idx,
+                                                 self.default_scan_setting)
+                self.log.debug("scan result %s %s", i, results)
+            event_name = "%s%sonLost" % (BSSID_EVENT_TAG, idx)
+            self.log.info("Waiting for the BSSID event %s", event_name)
+            event = self.dut.ed.pop_event(event_name, BSSID_EVENT_WAIT)
+            self.log.debug(event)
+        except queue.Empty as error:
+            raise AssertionError("Event %s did not triggered for %s\n%s" %
+                                 (event_name, track_setting["bssidInfos"],
+                                  error))
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(self.scan_idx)
+            if idx:
+                self.dut.droid.wifiScannerStopTrackingBssids(idx)
+
+    def wifi_generate_track_bssid_settings(self, isLost):
+        """Generates all the combinations of different track setting parameters.
+
+        Returns:
+            A list of dictionaries each representing a set of track settings.
+        """
+        bssids = [[self.bssid_2g], [self.bssid_5g],
+                  [self.bssid_2g, self.bssid_5g]]
+        if self.dut.model != "hammerhead" or not self.two_ap_testbed:
+            bssids.append([self.bssid_dfs])
+        if isLost:
+            apthreshold = (3, 5)
+        else:
+            apthreshold = (1, )
+        # Create track setting strings based on the combinations
+        setting_combinations = list(itertools.product(bssids, apthreshold))
+        # Create scan setting strings based on the combinations
+        track_settings = []
+        for combo in setting_combinations:
+            s = {}
+            s["bssidInfos"] = combo[0]
+            s["apLostThreshold"] = combo[1]
+            track_settings.append(s)
+        return track_settings
+
+    def track_setting_to_string(self, track_setting):
+        """Convert track setting to string for Bssids in that"""
+        string = ""
+        for bssid_setting in track_setting:
+            string += bssid_setting[wutils.WifiEnums.BSSID_KEY]
+            string += "_"
+        return string
+
+    def combineBssids(self, *track_settings):
+        """Combine bssids in the track_settings to one list"""
+        bssids = []
+        for track_setting in track_settings:
+            bssids.extend(track_setting["bssidInfos"])
+        return bssids
+
+    """ Helper Functions End """
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="599a30b8-73ad-4314-a245-7ec58fc7e74b")
+    def test_wifi_track_bssid_found(self):
+        """Test bssid track for event found with a list of different settings.
+
+         1. Starts Wifi Scanner bssid tracking for interested bssids in track_setting.
+         2. Start Wifi Scanner scan with default scan settings.
+         3. Validate the environment to check AP is not in range.
+         4. Attenuate the signal to make AP in range.
+         5. Verified that onFound event is triggered for interested bssids in
+            track setting.
+        """
+        track_settings = self.wifi_generate_track_bssid_settings(False)
+        name_func = lambda track_setting: "test_wifi_track_found_bssidInfos_%sapLostThreshold_%s" % (self.track_setting_to_string(track_setting["bssidInfos"]), track_setting["apLostThreshold"])
+        failed = self.run_generated_testcases(
+            self.track_bssid_with_vaild_scan_for_found,
+            track_settings,
+            name_func=name_func)
+        asserts.assert_false(
+            failed, "Track bssid found failed with these bssids: %s" % failed)
+
+    @test_tracker_info(uuid="7ebd4b61-c408-45b3-b9b6-098753d46aa7")
+    def test_wifi_track_bssid_lost(self):
+        """Test bssid track for event lost with a list of different settings.
+
+         1. Start Wifi Scanner scan with default scan settings.
+         2. Validate the environment to check AP is not in range.
+         3. Starts Wifi Scanner bssid tracking for interested bssids in track_setting.
+         4. Attenuate the signal to make Bssids in range.
+         5. Verified that onFound event is triggered for interested bssids in
+            track setting.
+         6. Attenuate the signal to make Bssids out of range.
+         7. Verified that onLost event is triggered.
+        """
+        track_settings = self.wifi_generate_track_bssid_settings(True)
+        name_func = lambda track_setting: "test_wifi_track_lost_bssidInfos_%sapLostThreshold_%s" % (self.track_setting_to_string(track_setting["bssidInfos"]), track_setting["apLostThreshold"])
+        failed = self.run_generated_testcases(
+            self.track_bssid_with_vaild_scan_for_lost,
+            track_settings,
+            name_func=name_func)
+        asserts.assert_false(
+            failed, "Track bssid lost failed with these bssids: %s" % failed)
+
+    def test_wifi_track_bssid_sanity(self):
+        """Test bssid track for event found and lost with default settings.
+
+         1. Start WifiScanner scan for default scan settings.
+         2. Start Bssid track for "bssid_2g" AP.
+         3. Attenuate the signal to move in AP range.
+         4. Verify that onFound event occur.
+         5. Attenuate the signal to move out of range
+         6. Verify that onLost event occur.
+        """
+        track_setting = {"bssidInfos": [self.bssid_2g], "apLostThreshold": 3}
+        self.track_bssid_with_vaild_scan_for_lost(track_setting)
+
+    def test_wifi_track_bssid_for_2g_while_scanning_5g_channels(self):
+        """Test bssid track for 2g bssids while scanning 5g channels.
+
+         1. Starts Wifi Scanner bssid tracking for 2g bssids in track_setting.
+         2. Start Wifi Scanner scan for 5G Band only.
+         3. Validate the environment to check AP is not in range.
+         4. Attenuate the signal to make AP in range.
+         5. Verified that onFound event isn't triggered for 2g bssids.
+      """
+        self.attenuators[self.attenuator_id].set_atten(90)
+        scan_setting = {"band": wutils.WifiEnums.WIFI_BAND_5_GHZ,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                        wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                        "numBssidsPerScan": 32}
+        track_setting = {"bssidInfos": [self.bssid_2g], "apLostThreshold": 3}
+        self.start_scan_and_validate_environment(scan_setting,
+                                                 track_setting["bssidInfos"])
+        idx = None
+        try:
+            data = wutils.start_wifi_track_bssid(self.dut, track_setting)
+            idx = data["Index"]
+            self.attenuators[self.attenuator_id].set_atten(0)
+            event_name = "%s%sonFound" % (BSSID_EVENT_TAG, idx)
+            self.log.info("Waiting for the BSSID event %s", event_name)
+            #waiting for 2x time to make sure
+            event = self.dut.ed.pop_event(event_name, BSSID_EVENT_WAIT * 2)
+            self.log.debug(event)
+            self.check_bssid_in_found_result(track_setting["bssidInfos"],
+                                             event["data"]["Results"])
+        except queue.Empty as error:
+            self.log.info(
+                "As excepted event didn't occurred with different scan setting")
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(self.scan_idx)
+            if idx:
+                self.dut.droid.wifiScannerStopTrackingBssids(idx)
+
+    def test_wifi_track_bssid_for_5g_while_scanning_2g_channels(self):
+        """Test bssid track for 5g bssids while scanning 2g channels.
+
+           1. Starts Wifi Scanner bssid tracking for 5g bssids in track_setting.
+           2. Start Wifi Scanner scan for 2G Band only.
+           3. Validate the environment to check AP is not in range.
+           4. Attenuate the signal to make AP in range.
+           5. Verified that onFound event isn't triggered for 5g bssids.
+        """
+        self.attenuators[self.attenuator_id].set_atten(90)
+        scan_setting = {"band": wutils.WifiEnums.WIFI_BAND_24_GHZ,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                        wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                        "numBssidsPerScan": 32}
+        track_setting = {"bssidInfos": [self.bssid_5g], "apLostThreshold": 3}
+        data = wutils.start_wifi_track_bssid(self.dut, track_setting)
+        idx = data["Index"]
+        self.start_scan_and_validate_environment(scan_setting,
+                                                 track_setting["bssidInfos"])
+        try:
+            self.attenuators[self.attenuator_id].set_atten(0)
+            event_name = "%s%sonFound" % (BSSID_EVENT_TAG, idx)
+            self.log.info("Waiting for the BSSID event %s", event_name)
+            #waiting for 2x time to make sure
+            event = self.dut.ed.pop_event(event_name, BSSID_EVENT_WAIT * 2)
+            self.log.debug(event)
+            self.check_bssid_in_found_result(track_setting["bssidInfos"],
+                                             event["data"]["Results"])
+        except queue.Empty as error:
+            self.log.info(
+                "As excepted event didn't occurred with different scan setting")
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(self.scan_idx)
+            if idx:
+                self.dut.droid.wifiScannerStopTrackingBssids(idx)
+
+    def test_wifi_tracking_bssid_multi_listeners_found(self):
+        """Test bssid tracking for multiple listeners
+            1. Start BSSID tracking for 5g bssids
+            2. Start BSSID tracking for 2g bssids
+            3. Start WifiScanner scan on both bands.
+            4. Valid the environment and check the APs are not in range.
+            5. Attenuate the signal to make the APs in range.
+            6. Verify onFound event triggered on both APs.
+        """
+        # Attenuate the signal to make APs invisible.
+        self.attenuators[self.attenuator_id].set_atten(90)
+        scan_setting = { "band": WifiEnums.WIFI_BAND_BOTH_WITH_DFS,
+                         "periodInMs": SCANTIME,
+                         "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                         "numBssidsPerScan": 32}
+        track_setting_5g = {"bssidInfos":[self.bssid_5g], "apLostThreshold":3}
+        data_5g = start_wifi_track_bssid(self.dut, track_setting_5g)
+        idx_5g = data_5g["Index"]
+
+        track_setting_2g = {"bssidInfos":[self.bssid_2g], "apLostThreshold":3}
+        data_2g = start_wifi_track_bssid(self.dut, track_setting_2g)
+        idx_2g = data_2g["Index"]
+
+        valid_env = self.start_scan_and_validate_environment(
+            scan_setting, self.combineBssids(track_setting_5g, track_setting_2g))
+        try:
+            asserts.assert_true(valid_env,
+                                "Test environment is not valid, AP is in range")
+            self.attenuators[self.attenuator_id].set_atten(0)
+            event_name = "{}{}{}{}onFound".format(BSSID_EVENT_TAG, idx_5g, BSSID_EVENT_TAG, idx_2g)
+            self.log.info("Waiting for the BSSID event {}".format(event_name))
+            #waiting for 2x time to make sure
+            event = self.dut.ed.pop_event(event_name, BSSID_EVENT_WAIT * 2)
+            self.log.debug(event)
+            found = self.check_bssid_in_found_result(
+                self.combineBssids(track_setting_5g, track_setting_2g),
+                event["data"]["Results"])
+            asserts.assert_true(found,
+                                "Test failed because Bssid onFound event is not triggered")
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(self.scan_idx)
+            if idx_5g:
+                self.dut.droid.wifiScannerStopTrackingBssids(idx_5g)
+            if idx_2g:
+                self.dut.droid.wifiScannerStopTrackingBssids(idx_2g);
+
+""" Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py b/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
new file mode 100755
index 0000000..da88012
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
@@ -0,0 +1,650 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts import base_test
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiChannelUS = wutils.WifiChannelUS
+WifiEnums = wutils.WifiEnums
+
+SCAN_EVENT_TAG = "WifiScannerScan"
+
+
+class WifiScanResultEvents():
+    """This class stores the setting of a scan, parameters generated
+    from starting the scan, and events reported later from the scan
+    for validation.
+
+    Attributes:
+        scan_setting: Setting used to perform the scan.
+        scan_channels: Channels used for scanning.
+        events: A list to store the scan result events.
+    """
+
+    def __init__(self, scan_setting, scan_channels):
+        self.scan_setting = scan_setting
+        self.scan_channels = scan_channels
+        self.results_events = []
+
+    def add_results_event(self, event):
+        self.results_events.append(event)
+
+    def check_interval(self, scan_result, scan_result_next):
+        """Verifies that the time gap between two consecutive results is within
+        expected range.
+
+        Right now it is hard coded to be 20 percent of the interval specified
+        by scan settings. This threshold can be imported from the configuration
+        file in the future if necessary later.
+
+        Note the scan result timestamps are in microseconds, but "periodInMs"
+        in scan settings is in milliseconds.
+
+        Args:
+            scan_result: A dictionary representing a scan result for a BSSID.
+            scan_result_next: A dictionary representing a scan result for a
+                BSSID, whose scan happened after scan_result.
+        """
+        actual_interval = scan_result_next["timestamp"] - scan_result[
+            "timestamp"]
+        expected_interval = self.scan_setting['periodInMs'] * 1000
+        delta = abs(actual_interval - expected_interval)
+        margin = expected_interval * 0.25  # 25% of the expected_interval
+        asserts.assert_true(
+            delta < margin, "The difference in time between scan %s and "
+            "%s is %dms, which is out of the expected range %sms" % (
+                scan_result, scan_result_next, delta / 1000,
+                self.scan_setting['periodInMs']))
+
+    def verify_one_scan_result(self, scan_result):
+        """Verifies the scan result of a single BSSID.
+
+        1. Verifies the frequency of the network is within the range requested
+        in the scan.
+
+        Args:
+            scan_result: A dictionary representing the scan result of a single
+                BSSID.
+        """
+        freq = scan_result["frequency"]
+        asserts.assert_true(
+            freq in self.scan_channels,
+            "Frequency %d of result entry %s is out of the expected range %s."
+            % (freq, scan_result, self.scan_channels))
+        # TODO(angli): add RSSI check.
+
+    def verify_one_scan_result_group(self, batch):
+        """Verifies a group of scan results obtained during one scan.
+
+        1. Verifies the number of BSSIDs in the batch is less than the
+        threshold set by scan settings.
+        2. Verifies each scan result for individual BSSID.
+
+        Args:
+            batch: A list of dictionaries, each dictionary represents a scan
+                result.
+        """
+        scan_results = batch["ScanResults"]
+        actual_num_of_results = len(scan_results)
+        expected_num_of_results = self.scan_setting['numBssidsPerScan']
+        asserts.assert_true(actual_num_of_results <= expected_num_of_results,
+                            "Expected no more than %d BSSIDs, got %d." %
+                            (expected_num_of_results, actual_num_of_results))
+        for scan_result in scan_results:
+            self.verify_one_scan_result(scan_result)
+
+    def have_enough_events(self):
+        """Check if there are enough events to properly validate the scan"""
+        return len(self.results_events) >= 2
+
+    def check_scan_results(self):
+        """Validate the reported scan results against the scan settings.
+        Assert if any error detected in the results.
+
+        1. For each scan setting there should be no less than 2 events received.
+        2. For batch scan, the number of buffered results in each event should
+           be exactly what the scan setting specified.
+        3. Each scan result should contain no more BBSIDs than what scan
+           setting specified.
+        4. The frequency reported by each scan result should comply with its
+           scan setting.
+        5. The time gap between two consecutive scan results should be
+           approximately equal to the scan interval specified by the scan
+           setting.
+        A scan result looks like this:
+        {
+          'data':
+           {
+             'Type': 'onResults',
+             'ResultElapsedRealtime': 4280931,
+             'Index': 10,
+             'Results': [
+                         {
+                          'Flags': 0,
+                          'Id': 4,
+                          'ScanResults':[
+                                          {
+                                           'is80211McRTTResponder': False,
+                                           'channelWidth': 0,
+                                           'numUsage': 0,
+                                           'SSID': '"wh_ap1_2g"',
+                                           'timestamp': 4280078660,
+                                           'BSSID': '30:b5:c2:33:f9:05',
+                                           'frequency': 2412,
+                                           'distanceSdCm': 0,
+                                           'distanceCm': 0,
+                                           'centerFreq1': 0,
+                                           'centerFreq0': 0,
+                                           'venueName': '',
+                                           'seen': 0,
+                                           'operatorFriendlyName': '',
+                                           'level': -31,
+                                           'passpointNetwork': False,
+                                           'untrusted': False
+                                          }
+                                        ]
+                         }
+                        ]
+            },
+          'time': 1491744576383,
+          'name': 'WifiScannerScan10onResults'
+        }
+        """
+        num_of_events = len(self.results_events)
+        asserts.assert_true(
+            num_of_events >= 2,
+            "Expected more than one scan result events, got %d." %
+            num_of_events)
+        for event_idx in range(num_of_events):
+            batches = self.results_events[event_idx]["data"]["Results"]
+            actual_num_of_batches = len(batches)
+            if not actual_num_of_batches:
+                raise signals.TestFailure("Scan returned empty Results list %s "
+                                          "% batches")
+            # For batch scan results.
+            report_type = self.scan_setting['reportEvents']
+            if not (report_type & WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN):
+                # Verifies that the number of buffered results matches the
+                # number defined in scan settings.
+                expected_num_of_batches = self.scan_setting['maxScansToCache']
+                asserts.assert_true(
+                    actual_num_of_batches <= expected_num_of_batches,
+                    "Expected to get at most %d batches in event No.%d, got %d."
+                    % (expected_num_of_batches, event_idx,
+                       actual_num_of_batches))
+            # Check the results within each event of batch scan
+            for batch_idx in range(actual_num_of_batches):
+                if not len(batches[batch_idx]["ScanResults"]):
+                    raise signals.TestFailure("Scan event %d returned empty"
+                    " scan results in batch %d" % (event_idx, batch_idx))
+                # Start checking interval from the second batch.
+                if batch_idx >=1:
+                    self.check_interval(
+                        batches[batch_idx - 1]["ScanResults"][0],
+                        batches[batch_idx]["ScanResults"][0])
+            for batch in batches:
+                self.verify_one_scan_result_group(batch)
+
+            # Check the time gap between the first result of an event and
+            # the last result of its previous event
+            # Skip the very first event.
+            if event_idx >= 1:
+                previous_batches = self.results_events[event_idx - 1]["data"][
+                    "Results"]
+                self.check_interval(previous_batches[-1]["ScanResults"][0],
+                                    batches[0]["ScanResults"][0])
+
+
+class WifiScannerMultiScanTest(WifiBaseTest):
+    """This class is the WiFi Scanner Multi-Scan Test suite.
+    It collects a number of test cases, sets up and executes
+    the tests, and validates the scan results.
+
+    Attributes:
+        tests: A collection of tests to excute.
+        leeway: Scan interval drift time (in seconds).
+        stime_channels: Dwell time plus 2ms.
+        dut: Android device(s).
+        wifi_chs: WiFi channels according to the device model.
+    """
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+        self.tests = (
+            'test_wifi_two_scans_at_same_interval',
+            'test_wifi_two_scans_at_different_interval',
+            'test_wifi_scans_24GHz_and_both',
+            'test_wifi_scans_5GHz_and_both',
+            'test_wifi_scans_batch_and_24GHz',
+            'test_wifi_scans_batch_and_5GHz',
+            'test_wifi_scans_24GHz_5GHz_full_result',)
+
+    def setup_class(self):
+        super().setup_class()
+        self.leeway = 5  # seconds, for event wait time computation
+        self.stime_channel = 47  #dwell time plus 2ms
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        asserts.assert_true(self.dut.droid.wifiIsScannerSupported(),
+                            "Device %s doesn't support WifiScanner, abort." %
+                            self.dut.model)
+        """ Setup the required dependencies and fetch the user params from
+        config file.
+        """
+        opt_param = ["reference_networks"]
+        self.unpack_userparams(opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True)
+
+        self.wifi_chs = WifiChannelUS(self.dut.model)
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """ Helper Functions Begin """
+
+    def start_scan(self, scan_setting):
+        data = wutils.start_wifi_background_scan(self.dut, scan_setting)
+        idx = data["Index"]
+        # Calculate event wait time from scan setting plus leeway
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        scan_period = scan_setting['periodInMs']
+        report_type = scan_setting['reportEvents']
+        if report_type & WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN:
+            scan_time += scan_period
+        else:
+            max_scan = scan_setting['maxScansToCache']
+            scan_time += max_scan * scan_period
+        wait_time = scan_time / 1000 + self.leeway
+        return idx, wait_time, scan_channels
+
+    def validate_scan_results(self, scan_results_dict):
+        # Check to make sure the dict is not empty
+        asserts.assert_true(scan_results_dict, "Scan result dict is empty.")
+        for scan_result_obj in scan_results_dict.values():
+            # Validate the results received for each scan setting
+            scan_result_obj.check_scan_results()
+
+    def wait_for_scan_events(self, wait_time_list, scan_results_dict):
+        """Poll for WifiScanner events and record them"""
+
+        # Compute the event wait time
+        event_wait_time = min(wait_time_list)
+
+        # Compute the maximum test time that guarantee that even the scan
+        # which requires the most wait time will receive at least two
+        # results.
+        max_wait_time = max(wait_time_list)
+        max_end_time = time.monotonic() + max_wait_time
+        self.log.debug("Event wait time %s seconds", event_wait_time)
+
+        try:
+            # Wait for scan results on all the caller specified bands
+            event_name = SCAN_EVENT_TAG
+            while True:
+                self.log.debug("Waiting for events '%s' for up to %s seconds",
+                               event_name, event_wait_time)
+                events = self.dut.ed.pop_events(event_name, event_wait_time)
+                for event in events:
+                    self.log.debug("Event received: %s", event)
+                    # Event name is the key to the scan results dictionary
+                    actual_event_name = event["name"]
+                    asserts.assert_true(
+                        actual_event_name in scan_results_dict,
+                        "Expected one of these event names: %s, got '%s'." %
+                        (scan_results_dict.keys(), actual_event_name))
+
+                    # TODO validate full result callbacks also
+                    if event["name"].endswith("onResults"):
+                        # Append the event
+                        scan_results_dict[actual_event_name].add_results_event(
+                            event)
+
+                # If we time out then stop waiting for events.
+                if time.monotonic() >= max_end_time:
+                    break
+                # If enough scan results have been returned to validate the
+                # results then break early.
+                have_enough_events = True
+                for key in scan_results_dict:
+                    if not scan_results_dict[key].have_enough_events():
+                        have_enough_events = False
+                if have_enough_events:
+                    break
+        except queue.Empty:
+            asserts.fail("Event did not trigger for {} in {} seconds".format(
+                event_name, event_wait_time))
+
+    def scan_and_validate_results(self, scan_settings):
+        """Perform WifiScanner scans and check the scan results
+
+        Procedures:
+          * Start scans for each caller specified setting
+          * Wait for at least two results for each scan
+          * Check the results received for each scan
+        """
+        # Awlays get a clean start
+        self.dut.ed.clear_all_events()
+
+        # Start scanning with the caller specified settings and
+        # compute parameters for receiving events
+        idx_list = []
+        wait_time_list = []
+        scan_results_dict = {}
+
+        try:
+            for scan_setting in scan_settings:
+                self.log.debug(
+                    "Scan setting: band %s, interval %s, reportEvents "
+                    "%s, numBssidsPerScan %s", scan_setting["band"],
+                    scan_setting["periodInMs"], scan_setting["reportEvents"],
+                    scan_setting["numBssidsPerScan"])
+                idx, wait_time, scan_chan = self.start_scan(scan_setting)
+                self.log.debug(
+                    "Scan started for band %s: idx %s, wait_time %ss, scan_channels %s",
+                    scan_setting["band"], idx, wait_time, scan_chan)
+                idx_list.append(idx)
+                wait_time_list.append(wait_time)
+
+                report_type = scan_setting['reportEvents']
+                scan_results_events = WifiScanResultEvents(scan_setting,
+                                                           scan_chan)
+                scan_results_dict["{}{}onResults".format(
+                    SCAN_EVENT_TAG, idx)] = scan_results_events
+                if (scan_setting['reportEvents']
+                        & WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT):
+                    scan_results_dict["{}{}onFullResult".format(
+                        SCAN_EVENT_TAG, idx)] = scan_results_events
+
+            self.wait_for_scan_events(wait_time_list, scan_results_dict)
+
+            # Validate the scan results
+            self.validate_scan_results(scan_results_dict)
+
+        finally:
+            # Tear down and clean up
+            for idx in idx_list:
+                self.dut.droid.wifiScannerStopBackgroundScan(idx)
+            self.dut.ed.clear_all_events()
+
+    """ Helper Functions End """
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="d490b146-5fc3-4fc3-9958-78ba0ad63211")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_two_scans_at_same_interval(self):
+        """Perform two WifiScanner background scans, one at 2.4GHz and the other
+        at 5GHz, the same interval and number of BSSIDs per scan.
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [{"band": WifiEnums.WIFI_BAND_24_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24},
+                         {"band": WifiEnums.WIFI_BAND_5_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24}]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="0ec9a554-f942-41a9-8096-6b0b400f60b0")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_two_scans_at_different_interval(self):
+        """Perform two WifiScanner background scans, one at 2.4GHz and the other
+        at 5GHz, different interval and number of BSSIDs per scan.
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [{"band": WifiEnums.WIFI_BAND_24_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 20},
+                         {"band": WifiEnums.WIFI_BAND_5_GHZ,
+                          "periodInMs": 30000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24}]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="0d616591-0d32-4be6-8fd4-e4a5e9ccdce0")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scans_24GHz_and_both(self):
+        """Perform two WifiScanner background scans, one at 2.4GHz and
+           the other at both 2.4GHz and 5GHz
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [{"band": WifiEnums.WIFI_BAND_BOTH,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24},
+                         {"band": WifiEnums.WIFI_BAND_24_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24}]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="ddcf959e-512a-4e86-b3d3-18cebd0b22a0")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scans_5GHz_and_both(self):
+        """Perform two WifiScanner scans, one at 5GHz and the other at both
+           2.4GHz and 5GHz
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [{"band": WifiEnums.WIFI_BAND_BOTH,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24},
+                         {"band": WifiEnums.WIFI_BAND_5_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24}]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="060469f1-fc6b-4255-ab6e-b1d5b54db53d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scans_24GHz_5GHz_and_DFS(self):
+        """Perform three WifiScanner scans, one at 5GHz, one at 2.4GHz and the
+        other at just 5GHz DFS channels
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [
+            {"band": WifiEnums.WIFI_BAND_5_GHZ_DFS_ONLY,
+             "periodInMs": 10000,  # ms
+             "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+             "numBssidsPerScan": 24},
+            {"band": WifiEnums.WIFI_BAND_5_GHZ,
+             "periodInMs": 10000,  # ms
+             "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+             "numBssidsPerScan": 24},
+            {"band": WifiEnums.WIFI_BAND_24_GHZ,
+             "periodInMs": 30000,  # ms
+             "reportEvents": WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+             "numBssidsPerScan": 24}
+        ]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="14104e98-27a0-43d5-9525-b36b65ac3957")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scans_batch_and_24GHz(self):
+        """Perform two WifiScanner background scans, one in batch mode for both
+        bands and the other in periodic mode at 2.4GHz
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of results in batch mode should match the setting
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [{"band": WifiEnums.WIFI_BAND_BOTH,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL,
+                          "numBssidsPerScan": 24,
+                          "maxScansToCache": 2},
+                         {"band": WifiEnums.WIFI_BAND_24_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24}]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="cd6064b5-840b-4334-8cd4-8320a6cda52f")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scans_batch_and_5GHz(self):
+        """Perform two WifiScanner background scans, one in batch mode for both
+        bands and the other in periodic mode at 5GHz
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of results in batch mode should match the setting
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [{"band": WifiEnums.WIFI_BAND_BOTH,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL,
+                          "numBssidsPerScan": 24,
+                          "maxScansToCache": 2},
+                         {"band": WifiEnums.WIFI_BAND_5_GHZ,
+                          "periodInMs": 10000,  # ms
+                          "reportEvents":
+                          WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+                          "numBssidsPerScan": 24}]
+
+        self.scan_and_validate_results(scan_settings)
+
+    @test_tracker_info(uuid="9f48cb0c-de87-4cd2-9e50-857579d44079")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scans_24GHz_5GHz_full_result(self):
+        """Perform two WifiScanner background scans, one at 2.4GHz and
+           the other at 5GHz. Report full scan results.
+
+        Initial Conditions:
+          * Set multiple APs broadcasting 2.4GHz and 5GHz.
+
+        Expected Results:
+          * DUT reports success for starting both scans
+          * Scan results for each callback contains only the results on the
+            frequency scanned
+          * Wait for at least two scan results and confirm that separation
+            between them approximately equals to the expected interval
+          * Number of BSSIDs doesn't exceed
+        """
+        scan_settings = [
+            {"band": WifiEnums.WIFI_BAND_24_GHZ,
+             "periodInMs": 10000,  # ms
+             "reportEvents": WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT
+             | WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+             "numBssidsPerScan": 24},
+            {"band": WifiEnums.WIFI_BAND_5_GHZ,
+             "periodInMs": 10000,  # ms
+             "reportEvents": WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT
+             | WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+             "numBssidsPerScan": 24}
+        ]
+
+        self.scan_and_validate_results(scan_settings)
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiScannerScanTest.py b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
new file mode 100755
index 0000000..f267078
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
@@ -0,0 +1,1094 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import queue
+import time
+import traceback
+
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_constants
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+SCANTIME = 10000  #framework support only 10s as minimum scan interval
+NUMBSSIDPERSCAN = 8
+EVENT_TAG = "WifiScannerScan"
+SCAN_TIME_PASSIVE = 47  # dwell time plus 2ms
+SCAN_TIME_ACTIVE = 32  # dwell time plus 2ms
+SHORT_TIMEOUT = 30
+NETWORK_ID_ERROR = "Network don't have ID"
+NETWORK_ERROR = "Device is not connected to reference network"
+INVALID_RESULT = "Test fail because scan result reported are not valid"
+EMPTY_RESULT = "Test fail because empty scan result reported"
+KEY_RET = "ResultElapsedRealtime"
+ATTENUATOR = 0
+
+class WifiScannerScanError(Exception):
+    pass
+
+
+class WifiScannerScanTest(WifiBaseTest):
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+        # TODO(angli): Remove this list.
+        # There are order dependencies among these tests so we'll have to leave
+        # it here for now. :(
+        self.tests = (
+            "test_available_channels_band_1",
+            "test_available_channels_band_2",
+            "test_available_channels_band_3",
+            "test_available_channels_band_4",
+            "test_available_channels_band_6",
+            "test_available_channels_band_7",
+            "test_wifi_scanner_single_scan_channel_sanity",
+            "test_wifi_scanner_with_wifi_off",
+            "test_single_scan_report_each_scan_for_channels_with_enumerated_params",
+            "test_single_scan_report_each_scan_for_band_with_enumerated_params",
+            "test_single_scan_report_full_scan_for_channels_with_enumerated_params",
+            "test_single_scan_report_full_scan_for_band_with_enumerated_params",
+            "test_single_scan_while_pno",
+            "test_wifi_scanner_single_scan_in_isolated",
+            "test_wifi_scanner_with_invalid_numBssidsPerScan",
+            "test_wifi_scanner_dual_radio_low_latency",
+            "test_wifi_scanner_dual_radio_low_power",
+            "test_wifi_scanner_dual_radio_high_accuracy")
+
+    def setup_class(self):
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ("run_extended_test", "ping_addr", "dbs_supported_models")
+        opt_param = ["reference_networks"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(ap_count=2, mirror_ap=False)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                wpa_network=True,
+                                                ap_count=2)
+
+        self.leeway = 10
+        self.stime_channel = SCAN_TIME_PASSIVE
+        self.default_scan_setting = {
+            "band": wutils.WifiEnums.WIFI_BAND_BOTH,
+            "periodInMs": SCANTIME,
+            "reportEvents": wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN
+        }
+        self.default_batch_scan_setting = {
+            "band": wutils.WifiEnums.WIFI_BAND_BOTH,
+            "periodInMs": SCANTIME,
+            "reportEvents": wutils.WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL
+        }
+        self.log.debug("Run extended test: {}".format(self.run_extended_test))
+        self.wifi_chs = wutils.WifiChannelUS(self.dut.model)
+        self.attenuators = wutils.group_attenuators(self.attenuators)
+        self.attenuators[0].set_atten(0)
+        self.attenuators[1].set_atten(0)
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.log.debug("Shut down all wifi scanner activities.")
+        self.dut.droid.wifiScannerShutdown()
+
+    def teardown_class(self):
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """ Helper Functions Begin """
+
+    def wifi_generate_scanner_scan_settings(self, extended, scan_type,
+                                            report_result):
+        """Generates all the combinations of different scan setting parameters.
+
+        Args:
+          extended: True for extended setting
+          scan_type: key for type of scan
+          report_result: event type of report scan results
+
+        Returns:
+          A list of dictionaries each representing a set of scan settings.
+        """
+        base_scan_time = [SCANTIME * 2]
+        if scan_type == "band":
+            scan_types_setting = [wutils.WifiEnums.WIFI_BAND_BOTH]
+        else:
+            scan_types_setting = [self.wifi_chs.MIX_CHANNEL_SCAN]
+        num_of_bssid = [NUMBSSIDPERSCAN * 4]
+        max_scan_cache = [0]
+        if extended:
+            base_scan_time.append(SCANTIME)
+            if scan_type == "band":
+                scan_types_setting.extend(
+                    [wutils.WifiEnums.WIFI_BAND_24_GHZ,
+                     wutils.WifiEnums.WIFI_BAND_5_GHZ_WITH_DFS,
+                     wutils.WifiEnums.WIFI_BAND_BOTH_WITH_DFS])
+            else:
+                scan_types_setting.extend(
+                    [self.wifi_chs.NONE_DFS_5G_FREQUENCIES, self.wifi_chs.
+                     ALL_2G_FREQUENCIES, self.wifi_chs.DFS_5G_FREQUENCIES,
+                     self.wifi_chs.ALL_5G_FREQUENCIES])
+            num_of_bssid.append(NUMBSSIDPERSCAN * 3)
+            max_scan_cache.append(5)
+            # Generate all the combinations of report types and scan types
+        if report_result == wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT:
+            report_types = {"reportEvents": report_result}
+            setting_combinations = list(itertools.product(scan_types_setting,
+                                                          base_scan_time))
+            # Create scan setting strings based on the combinations
+            scan_settings = []
+            for combo in setting_combinations:
+                s = dict(report_types)
+                s[scan_type] = combo[0]
+                s["periodInMs"] = combo[1]
+                scan_settings.append(s)
+        else:
+            report_types = {"reportEvents": report_result}
+            setting_combinations = list(
+                itertools.product(scan_types_setting, base_scan_time,
+                                  num_of_bssid, max_scan_cache))
+            # Create scan setting strings based on the combinations
+            scan_settings = []
+            for combo in setting_combinations:
+                s = dict(report_types)
+                s[scan_type] = combo[0]
+                s["periodInMs"] = combo[1]
+                s["numBssidsPerScan"] = combo[2]
+                s["maxScansToCache"] = combo[3]
+                scan_settings.append(s)
+        return scan_settings
+
+    def proces_and_valid_batch_scan_result(self, scan_resutls, scan_rt,
+                                           result_rt, scan_setting):
+        """This function process scan results and validate against settings used
+        while starting the scan.
+
+        There are two steps for the verification. First it checks that all the
+        wifi networks in results are of the correct frequencies set by scan setting
+        params. Then it checks that the delta between the batch of scan results less
+        than the time required for scanning channel set by scan setting params.
+
+        Args:
+            scan_results: scan results reported.
+            scan_rt: Elapsed real time on start scan.
+            result_rt: Elapsed ral time on results reported.
+            scan_setting: The params for the single scan.
+
+        Returns:
+            bssids: total number of bssids scan result have
+            validity: True if the all scan result are valid.
+        """
+        bssids = 0
+        validity = True
+        scan_time_mic = 0
+        scan_channels = []
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        scan_time_mic = scan_time * 1000
+        for i, batch in enumerate(scan_resutls, start=1):
+            asserts.assert_true(
+                batch["ScanResults"],
+                "At least one scan result is required to validate")
+            max_scan_interval = batch["ScanResults"][0][
+                "timestamp"] + scan_time_mic
+            self.log.debug("max_scan_interval: %s", max_scan_interval)
+            for result in batch["ScanResults"]:
+                # Though the tests are run in shield box, there are leakes
+                # from outside environment. This would ignore any such SSIDs
+                ssid = result["SSID"]
+                if not ssid.startswith("2g_") or not ssid.startswith("5g_"):
+                    continue
+                if (result["frequency"] not in scan_channels or
+                        result["timestamp"] > max_scan_interval or
+                        result["timestamp"] < scan_rt * 1000 or
+                        result["timestamp"] > result_rt * 1000):
+                    self.log.error("Result didn't match requirement: %s",
+                                   result)
+                    validity = False
+            self.log.info("Number of scan result in batch %s: %s", i,
+                          len(batch["ScanResults"]))
+            bssids += len(batch["ScanResults"])
+        return bssids, validity
+
+    def pop_scan_result_events(self, event_name):
+        """Function to pop all the scan result events.
+
+        Args:
+            event_name: event name.
+
+        Returns:
+            results: list  of scan result reported in events
+        """
+        results = []
+        try:
+            events = self.dut.ed.pop_all(event_name)
+            for event in events:
+                results.append(event["data"]["Results"])
+        except queue.Empty as error:
+            self.log.debug("Number of Full scan results %s", len(results))
+        return results
+
+    def wifi_scanner_single_scan(self, scan_setting):
+        """Common logic for an enumerated wifi scanner single scan test case.
+
+         1. Start WifiScanner single scan for scan_setting.
+         2. Wait for the scan result event, wait time depend on scan settings
+            parameter.
+         3. Verify that scan results match with scan settings parameters.
+         4. Also verify that only one scan result event trigger.
+
+        Args:
+            scan_setting: The params for the single scan.
+        """
+        data = wutils.start_wifi_single_scan(self.dut, scan_setting)
+        idx = data["Index"]
+        scan_rt = data["ScanElapsedRealtime"]
+        self.log.info(
+            "Wifi single shot scan started index: %s at real time: %s", idx,
+            scan_rt)
+        results = []
+        #generating event wait time from scan setting plus leeway
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        wait_time = int(scan_time / 1000) + self.leeway
+        validity = False
+        #track number of result received
+        result_received = 0
+        try:
+            for snumber in range(1, 3):
+                event_name = "{}{}onResults".format(EVENT_TAG, idx)
+                self.log.debug("Waiting for event: %s for time %s", event_name,
+                               wait_time)
+                event = self.dut.ed.pop_event(event_name, wait_time)
+                self.log.debug("Event received: %s", event)
+                results = event["data"]["Results"]
+                result_received += 1
+                bssids, validity = self.proces_and_valid_batch_scan_result(
+                    results, scan_rt, event["data"][KEY_RET], scan_setting)
+                asserts.assert_equal(
+                    len(results), 1,
+                    "Test fail because number of scan result %s" %
+                    len(results))
+                asserts.assert_true(bssids > 0, EMPTY_RESULT)
+                asserts.assert_true(validity, INVALID_RESULT)
+                self.log.info("Scan number Buckets: %s\nTotal BSSID: %s",
+                              len(results), bssids)
+        except queue.Empty as error:
+            asserts.assert_true(
+                result_received >= 1,
+                "Event did not triggered for single shot {}".format(error))
+        finally:
+            self.dut.droid.wifiScannerStopScan(idx)
+            #For single shot number of result received and length of result should be one
+            asserts.assert_true(
+                result_received == 1,
+                "Test fail because received result {}".format(result_received))
+
+    def wifi_scanner_single_scan_full(self, scan_setting):
+        """Common logic for single scan test case for full scan result.
+
+        1. Start WifiScanner single scan with scan_setting for full scan result.
+        2. Wait for the scan result event, wait time depend on scan settings
+           parameter.
+        3. Pop all full scan result events occurred earlier.
+        4. Verify that full scan results match with normal scan results.
+        5. If the scan type is included in scan_setting, verify that the
+           radioChainInfos length.
+
+        Args:
+            scan_setting: The parameters for the single scan.
+        """
+        self.dut.ed.clear_all_events()
+        data = wutils.start_wifi_single_scan(self.dut, scan_setting)
+        idx = data["Index"]
+        scan_rt = data["ScanElapsedRealtime"]
+        self.log.info("Wifi single shot scan started with index: %s", idx)
+        #generating event wait time from scan setting plus leeway
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        wait_time = int(scan_time / 1000) + self.leeway
+        results = []
+        validity = False
+        try:
+            event_name = "%s%sonResults" % (EVENT_TAG, idx)
+            self.log.debug("Waiting for event: %s for time %s", event_name,
+                           wait_time)
+            event = self.dut.ed.pop_event(event_name, wait_time)
+            self.log.info("Event received: %s", event)
+            bssids, validity = (self.proces_and_valid_batch_scan_result(
+                event["data"]["Results"], scan_rt, event["data"][KEY_RET],
+                scan_setting))
+            asserts.assert_true(bssids > 0, EMPTY_RESULT)
+            asserts.assert_true(validity, INVALID_RESULT)
+            event_name = "{}{}onFullResult".format(EVENT_TAG, idx)
+            results = self.pop_scan_result_events(event_name)
+            asserts.assert_true(
+                len(results) >= bssids,
+                "Full single shot result don't match {}".format(len(results)))
+            if 'type' in scan_setting.keys():
+                for item in results:
+                    self.verify_radio_chain_length(scan_setting['type'], item)
+        except queue.Empty as error:
+            raise AssertionError(
+                "Event did not triggered for single shot {}".format(error))
+        finally:
+            self.dut.droid.wifiScannerStopScan(idx)
+
+    def verify_radio_chain_length(self, scan_setting_type, scan_result):
+        llen = len(scan_result[0]["radioChainInfos"])
+        if scan_setting_type == wutils.WifiEnums.SCAN_TYPE_LOW_LATENCY \
+            or scan_setting_type == wutils.WifiEnums.SCAN_TYPE_LOW_POWER:
+            asserts.assert_true(llen == 1,
+                                "radioChainInfos len expected:{} "
+                                "actual:{}".format(1, llen))
+        else:
+            asserts.assert_true(llen == 2,
+                                "radioChainInfos len expected:{} "
+                                "actual:{}".format(2, llen))
+
+    def wifi_scanner_batch_scan_full(self, scan_setting):
+        """Common logic for batch scan test case for full scan result.
+
+        1. Start WifiScanner batch scan with scan_setting for full scan result.
+        2. Wait for the scan result event, wait time depend on scan settings
+           parameter.
+        3. Pop all full scan result events occurred earlier.
+        4. Verify that full scan results match with scan results.
+
+        Args:
+            scan_setting: The params for the batch scan.
+        """
+        self.dut.ed.clear_all_events()
+        data = wutils.start_wifi_background_scan(self.dut, scan_setting)
+        idx = data["Index"]
+        scan_rt = data["ScanElapsedRealtime"]
+        self.log.info("Wifi batch shot scan started with index: %s", idx)
+        #generating event wait time from scan setting plus leeway
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        # multiply scan period by two to account for scheduler changing period
+        scan_time += scan_setting[
+            'periodInMs'] * 2  #add scan period delay for next cycle
+        wait_time = scan_time / 1000 + self.leeway
+        validity = False
+        try:
+            for snumber in range(1, 3):
+                results = []
+                event_name = "%s%sonResults" % (EVENT_TAG, idx)
+                self.log.debug("Waiting for event: %s for time %s", event_name,
+                               wait_time)
+                event = self.dut.ed.pop_event(event_name, wait_time)
+                self.log.debug("Event received: %s", event)
+                bssids, validity = self.proces_and_valid_batch_scan_result(
+                    event["data"]["Results"], scan_rt, event["data"][KEY_RET],
+                    scan_setting)
+                event_name = "%s%sonFullResult" % (EVENT_TAG, idx)
+                results = self.pop_scan_result_events(event_name)
+                asserts.assert_true(
+                    len(results) >= bssids,
+                    "Full single shot result don't match %s" % len(results))
+                asserts.assert_true(bssids > 0, EMPTY_RESULT)
+                asserts.assert_true(validity, INVALID_RESULT)
+        except queue.Empty as error:
+            raise AssertionError("Event did not triggered for batch scan %s" %
+                                 error)
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(idx)
+            self.dut.ed.clear_all_events()
+
+    def wifi_scanner_batch_scan(self, scan_setting):
+        """Common logic for an enumerated wifi scanner batch scan test case.
+
+        1. Start WifiScanner batch scan for given scan_setting.
+        2. Wait for the scan result event, wait time depend on scan settings
+           parameter.
+        3. Verify that scan results match with scan settings parameters.
+        4. Also verify that multiple scan result events trigger.
+
+        Args:
+            scan_setting: The parameters for the batch scan.
+        """
+        data = wutils.start_wifi_background_scan(self.dut, scan_setting)
+        idx = data["Index"]
+        scan_rt = data["ScanElapsedRealtime"]
+        self.log.info(
+            "Wifi background scan started with index: %s real time %s", idx,
+            scan_rt)
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, scan_setting, self.stime_channel)
+        #generating event wait time from scan setting plus leeway
+        time_cache = 0
+        number_bucket = 1  #bucket for Report result on each scan
+        check_get_result = False
+        if scan_setting[
+                'reportEvents'] == wutils.WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL:
+            check_get_result = True
+            if ('maxScansToCache' in scan_setting and
+                    scan_setting['maxScansToCache'] != 0):
+                time_cache = (scan_setting['maxScansToCache'] *
+                              scan_setting['periodInMs'])
+                number_bucket = scan_setting['maxScansToCache']
+            else:
+                time_cache = 10 * scan_setting['periodInMs'
+                                               ]  #10 as default max scan cache
+                number_bucket = 10
+        else:
+            time_cache = scan_setting[
+                'periodInMs'
+            ]  #need while waiting for seconds resutls
+        # multiply cache time by two to account for scheduler changing period
+        wait_time = (time_cache * 2 + scan_time) / 1000 + self.leeway
+        validity = False
+        try:
+            for snumber in range(1, 3):
+                event_name = "%s%sonResults" % (EVENT_TAG, idx)
+                self.log.info("Waiting for event: %s for time %s", event_name,
+                              wait_time)
+                event = self.dut.ed.pop_event(event_name, wait_time)
+                self.log.debug("Event received: %s", event)
+                results = event["data"]["Results"]
+                bssids, validity = (self.proces_and_valid_batch_scan_result(
+                    results, scan_rt, event["data"][KEY_RET], scan_setting))
+                self.log.info("Scan number: %s\n Buckets: %s\n  BSSID: %s",
+                              snumber, len(results), bssids)
+                asserts.assert_equal(
+                    len(results), number_bucket,
+                    "Test fail because number_bucket %s" % len(results))
+                asserts.assert_true(bssids >= 1, EMPTY_RESULT)
+                asserts.assert_true(validity, INVALID_RESULT)
+                if snumber % 2 == 1 and check_get_result:
+                    self.log.info("Get Scan result using GetScanResult API")
+                    time.sleep(wait_time / number_bucket)
+                    if self.dut.droid.wifiScannerGetScanResults():
+                        event = self.dut.ed.pop_event(event_name, 1)
+                        self.log.debug("Event onResults: %s", event)
+                        results = event["data"]["Results"]
+                        bssids, validity = self.proces_and_valid_batch_scan_result(
+                            results, scan_rt, event["data"][KEY_RET],
+                            scan_setting)
+                        self.log.info("Got Scan result number: %s BSSID: %s",
+                                      snumber, bssids)
+                        asserts.assert_true(bssids >= 1, EMPTY_RESULT)
+                        asserts.assert_true(validity, INVALID_RESULT)
+                    else:
+                        self.log.error("Error while fetching the scan result")
+        except queue.Empty as error:
+            raise AssertionError("Event did not triggered for batch scan %s" %
+                                 error)
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(idx)
+            self.dut.ed.clear_all_events()
+
+    def start_wifi_scanner_single_scan_expect_failure(self, scan_setting):
+        """Common logic to test wif scanner single scan with invalid settings
+           or environment
+
+         1. Start WifiScanner batch scan for setting parameters.
+         2. Verify that scan is not started.
+
+         Args:
+            scan_setting: The params for the single scan.
+        """
+        try:
+            idx = self.dut.droid.wifiScannerStartScan(scan_setting)
+            event = self.dut.ed.pop_event(
+                "{}{}onFailure".format(EVENT_TAG, idx), SHORT_TIMEOUT)
+        except queue.Empty as error:
+            raise AssertionError("Did not get expected onFailure {}".format(
+                error))
+
+    def start_wifi_scanner_background_scan_expect_failure(self, scan_setting):
+        """Common logic to test wif scanner batch scan with invalid settings
+           or environment
+
+         1. Start WifiScanner batch scan for setting parameters.
+         2. Verify that scan is not started.
+
+         Args:
+          scan_setting: The params for the single scan.
+        """
+        try:
+            idx = self.dut.droid.wifiScannerStartBackgroundScan(scan_setting)
+            event = self.dut.ed.pop_event(
+                "{}{}onFailure".format(EVENT_TAG, idx), SHORT_TIMEOUT)
+        except queue.Empty as error:
+            raise AssertionError("Did not get expected onFailure {}".format(
+                error))
+
+    def check_get_available_channels_with_one_band(self, band):
+        """Common logic to check available channels for a band.
+
+         1. Get available channels for band.
+         2. Verify that channels match with supported channels for band.
+
+         Args:
+            band: wifi band."""
+
+        r = self.dut.droid.wifiScannerGetAvailableChannels(band)
+        self.log.info("Band: %s" % band)
+        self.log.info("Available channels: %s" % r)
+        expected = self.wifi_chs.band_to_freq(band)
+        self.log.info("Expected channels: %s" % expected)
+        asserts.assert_equal(set(r), set(expected), "Band %s failed." % band)
+
+    def connect_to_reference_network(self):
+        """Connect to reference network and make sure that connection happen"""
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        try:
+            self.dut.droid.wifiConnectByConfig(self.reference_networks[0]["2g"])
+            connect_result = self.dut.ed.pop_event(
+                wifi_constants.CONNECT_BY_CONFIG_SUCCESS, SHORT_TIMEOUT)
+            self.log.info(connect_result)
+            return wutils.track_connection(self.dut,
+                self.reference_networks[0]["2g"]["SSID"], 1)
+        except Exception as error:
+            self.log.exception(traceback.format_exc())
+            self.log.error("Connection to network fail because %s", error)
+            return False
+        finally:
+            self.dut.droid.wifiLockRelease()
+            self.dut.droid.goToSleepNow()
+
+    """ Helper Functions End """
+    """ Tests Begin """
+
+    # Test channels
+    """ Test available channels for different bands.
+
+        1. Get available channels for different bands.
+        2. Verify that channels match with supported channels for respective band.
+    """
+    @test_tracker_info(uuid="7cca8142-529f-4951-ab6f-cd03b359b3cc")
+    def test_available_channels_band_1(self):
+        self.check_get_available_channels_with_one_band(1)
+
+    @test_tracker_info(uuid="612afda1-0d74-4d2f-bc37-72ef2b98310a")
+    def test_available_channels_band_2(self):
+        self.check_get_available_channels_with_one_band(2)
+
+    @test_tracker_info(uuid="a9275bb9-afa7-4dd4-b2e0-60296ffd33bb")
+    def test_available_channels_band_3(self):
+        self.check_get_available_channels_with_one_band(3)
+
+    @test_tracker_info(uuid="5413632e-ce72-4ecc-bf9b-33ac9e4bf3fc")
+    def test_available_channels_band_4(self):
+        self.check_get_available_channels_with_one_band(4)
+
+    @test_tracker_info(uuid="a8f40b4f-d79d-4d2f-bed8-3b139a082f6c")
+    def test_available_channels_band_6(self):
+        self.check_get_available_channels_with_one_band(6)
+
+    @test_tracker_info(uuid="84cdfc25-8e64-42c7-b7f9-0a04e45d78b6")
+    def test_available_channels_band_7(self):
+        self.check_get_available_channels_with_one_band(7)
+
+    @test_tracker_info(uuid="95069244-b76c-4834-b3a6-96b0d8da98d8")
+    def test_single_scan_report_each_scan_for_channels_with_enumerated_params(
+            self):
+        """Test WiFi scanner single scan for channels with enumerated settings.
+
+         1. Start WifiScanner single scan for different channels with enumerated
+            scan settings.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "channels",
+            wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN)
+        self.log.debug("Scan settings: %s\n%s", len(scan_settings),
+                       scan_settings)
+        self.wifi_scanner_single_scan(scan_settings[0])
+
+    @test_tracker_info(uuid="5595ebe5-6d91-4379-a606-be59967e5ec9")
+    def test_single_scan_report_each_scan_for_band_with_enumerated_params(
+            self):
+        """Test WiFi scanner single scan for bands with enumerated settings.
+
+         1. Start WifiScanner single scan for different bands with enumerated
+            scan settings.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "band",
+            wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN)
+        self.log.debug("Scan settings:%s\n%s", len(scan_settings),
+                       scan_settings)
+        self.wifi_scanner_single_scan(scan_settings[0])
+
+    @test_tracker_info(uuid="44989f93-e63b-4c2e-a90a-a483477303bb")
+    def test_batch_scan_report_buffer_full_for_channels_with_enumerated_params(
+            self):
+        """Test WiFi scanner batch scan using channels with enumerated settings
+           to report buffer full scan results.
+
+         1. Start WifiScanner batch scan using different channels with enumerated
+            scan settings to report buffer full scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "channels",
+            wutils.WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL)
+        self.log.debug("Scan settings:%s\n%s", len(scan_settings),
+                       scan_settings)
+        self.wifi_scanner_batch_scan(scan_settings[0])
+
+    @test_tracker_info(uuid="63538df6-388a-4c16-964f-e9c19b750e07")
+    def test_batch_scan_report_buffer_full_for_band_with_enumerated_params(
+            self):
+        """Test WiFi scanner batch scan using band with enumerated settings
+           to report buffer full scan results.
+
+         1. Start WifiScanner batch scan using different bands with enumerated
+            scan settings to report buffer full scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "band",
+            wutils.WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL)
+        self.log.debug("Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_batch_scan(scan_settings[0])
+
+    @test_tracker_info(uuid="bd4e8c53-16c8-4ed6-b680-55c1ba688ad8")
+    def test_batch_scan_report_each_scan_for_channels_with_enumerated_params(
+            self):
+        """Test WiFi scanner batch scan using channels with enumerated settings
+           to report each scan results.
+
+         1. Start WifiScanner batch scan using different channels with enumerated
+            scan settings to report each scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "channels",
+            wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN)
+        self.log.debug("Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_batch_scan(scan_settings[0])
+
+    @test_tracker_info(uuid="d11e8c09-97d0-49c1-bf09-b9ec672c2fa6")
+    def test_batch_scan_report_each_scan_for_band_with_enumerated_params(self):
+        """Test WiFi scanner batch scan using band with enumerated settings
+           to report each scan results.
+
+         1. Start WifiScanner batch scan using different bands with enumerated
+            scan settings to report each scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "band",
+            wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN)
+        self.log.debug("Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_batch_scan(scan_settings[0])
+
+    @test_tracker_info(uuid="7f967b0e-82fe-403e-9d74-0dee7f09a21d")
+    def test_single_scan_report_full_scan_for_channels_with_enumerated_params(
+            self):
+        """Test WiFi scanner single scan using channels with enumerated settings
+           to report full scan results.
+
+         1. Start WifiScanner single scan using different channels with enumerated
+            scan settings to report full scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "channels",
+            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT)
+        self.log.debug("Full Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_single_scan_full(scan_settings[0])
+
+    @test_tracker_info(uuid="34d09f60-31bf-4952-8fb3-03fc93ec98fa")
+    def test_single_scan_report_full_scan_for_band_with_enumerated_params(
+            self):
+        """Test WiFi scanner single scan using band with enumerated settings
+           to report full scan results.
+
+         1. Start WifiScanner single scan using different bands with enumerated
+            scan settings to report full scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "band",
+            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT)
+        self.log.debug("Full Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_single_scan_full(scan_settings[0])
+
+    @test_tracker_info(uuid="0ddccf2e-b518-45a7-ae75-96924070b841")
+    def test_batch_scan_report_full_scan_for_channels_with_enumerated_params(
+            self):
+        """Test WiFi scanner batch scan using channels with enumerated settings
+           to report full scan results.
+
+         1. Start WifiScanner batch scan using different channels with enumerated
+            scan settings to report full scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "channels",
+            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT)
+        self.log.debug("Full Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_batch_scan_full(scan_settings[0])
+
+    @test_tracker_info(uuid="0685b667-8470-43a0-923d-dee71428f8ce")
+    def test_batch_scan_report_full_scan_for_band_with_enumerated_params(self):
+        """Test WiFi scanner batch scan using channels with enumerated settings
+           to report full scan results.
+
+         1. Start WifiScanner batch scan using different channels with enumerated
+            scan settings to report full scan results.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_settings = self.wifi_generate_scanner_scan_settings(
+            self.run_extended_test, "band",
+            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT)
+        self.log.debug("Full Scan settings:{}\n{}".format(
+            len(scan_settings), scan_settings))
+        self.wifi_scanner_batch_scan_full(scan_settings[0])
+
+    @test_tracker_info(uuid="e9a7cfb5-21c4-4c40-8169-8d88b65a1dee")
+    @WifiBaseTest.wifi_test_wrap
+    def test_single_scan_while_pno(self):
+        """Test wifi scanner single scan parallel to PNO connection.
+
+         1. Check device have a saved network.
+         2. Trigger PNO by attenuate the signal to move out of range.
+         3. Start WifiScanner single scan for both band with default scan settings.
+         4. Verify that scanner report single scan results.
+         5. Attenuate the signal to move in range.
+         6. Verify connection occurred through PNO.
+        """
+        self.log.info("Check connection through PNO for reference network")
+        self.attenuators[ATTENUATOR].set_atten(0)
+        asserts.assert_true(self.connect_to_reference_network(), NETWORK_ERROR)
+        time.sleep(10)  #wait for connection to be active
+        asserts.assert_true(
+            wutils.validate_connection(self.dut, self.ping_addr),
+            "Error, No internet connection for current network")
+
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Current network: {}".format(current_network))
+        asserts.assert_true('network_id' in current_network, NETWORK_ID_ERROR)
+        asserts.assert_true(current_network['network_id'] >= 0, NETWORK_ERROR)
+        self.log.info("Kicking PNO for reference network")
+        self.attenuators[ATTENUATOR].set_atten(90)
+        time.sleep(10)  #wait for PNO to be kicked
+        self.log.info("Starting single scan while PNO")
+        self.wifi_scanner_single_scan(self.default_scan_setting)
+        self.attenuators[ATTENUATOR].set_atten(0)
+        self.log.info("Check connection through PNO for reference network")
+        time.sleep(60)  #wait for connection through PNO
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Current network: {}".format(current_network))
+        asserts.assert_true('network_id' in current_network, NETWORK_ID_ERROR)
+        asserts.assert_true(current_network['network_id'] >= 0, NETWORK_ERROR)
+        time.sleep(10)  #wait for IP to be assigned
+        asserts.assert_true(
+            wutils.validate_connection(self.dut, self.ping_addr),
+            "Error, No internet connection for current network")
+        wutils.wifi_forget_network(self.dut,
+            self.reference_networks[0]["2g"]["SSID"])
+
+    @test_tracker_info(uuid="fc18d947-0b5a-42b4-98f3-dd1f2b52a7af")
+    def test_wifi_connection_and_pno_while_batch_scan(self):
+        """Test configuring a connection and PNO connection parallel to wifi
+           scanner batch scan.
+
+         1. Start WifiScanner batch scan with default batch scan settings.
+         2. Wait for scan result event for a time depend on scan settings.
+         3. Verify reported batch scan results.
+         4. Configure a connection to reference network.
+         5. Verify that connection to reference network occurred.
+         6. Wait for scan result event for a time depend on scan settings.
+         7. Verify reported batch scan results.
+         8. Trigger PNO by attenuate the signal to move out of range.
+         9. Wait for scan result event for a time depend on scan settings.
+         10. Verify reported batch scan results.
+         11. Attenuate the signal to move in range.
+         12. Verify connection occurred through PNO.
+        """
+        self.attenuators[ATTENUATOR].set_atten(0)
+        data = wutils.start_wifi_background_scan(
+            self.dut, self.default_batch_scan_setting)
+        idx = data["Index"]
+        scan_rt = data["ScanElapsedRealtime"]
+        self.log.info(
+            "Wifi background scan started with index: {} rt {}".format(
+                idx, scan_rt))
+        #generating event wait time from scan setting plus leeway
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, self.default_batch_scan_setting, self.stime_channel)
+        #default number buckets
+        number_bucket = 10
+        time_cache = self.default_batch_scan_setting[
+            'periodInMs'] * number_bucket  #default cache
+        #add 2 seconds extra time for switch between the channel for connection scan
+        #multiply cache time by two to account for scheduler changing period
+        wait_time = (time_cache * 2 + scan_time) / 1000 + self.leeway + 2
+        result_flag = 0
+        try:
+            for snumber in range(1, 7):
+                event_name = "{}{}onResults".format(EVENT_TAG, idx)
+                self.log.info("Waiting for event: {}".format(event_name))
+                event = self.dut.ed.pop_event(event_name, wait_time)
+                self.log.debug("Event onResults: {}".format(event))
+                results = event["data"]["Results"]
+                bssids, validity = self.proces_and_valid_batch_scan_result(
+                    results, scan_rt, event["data"][KEY_RET],
+                    self.default_batch_scan_setting)
+                self.log.info(
+                    "Scan number: {}\n Buckets: {}\n BSSID: {}".format(
+                        snumber, len(results), bssids))
+                asserts.assert_true(bssids >= 1,
+                                    "Not able to fetch scan result")
+                if snumber == 1:
+                    self.log.info(
+                        "Try to connect AP while waiting for event: {}".format(
+                            event_name))
+                    asserts.assert_true(self.connect_to_reference_network(),
+                                        NETWORK_ERROR)
+                    time.sleep(10)  #wait for connection to be active
+                    asserts.assert_true(
+                        wutils.validate_connection(self.dut, self.ping_addr),
+                        "Error, No internet connection for current network")
+                elif snumber == 3:
+                    self.log.info("Kicking PNO for reference network")
+                    self.attenuators[ATTENUATOR].set_atten(90)
+                elif snumber == 4:
+                    self.log.info("Bring back device for PNO connection")
+                    current_network = self.dut.droid.wifiGetConnectionInfo()
+                    self.log.info("Current network: {}".format(
+                        current_network))
+                    asserts.assert_true('network_id' in current_network,
+                                        NETWORK_ID_ERROR)
+                    asserts.assert_true(
+                        current_network['network_id'] == -1,
+                        "Device is still connected to network  {}".format(
+                            current_network[wutils.WifiEnums.SSID_KEY]))
+                    self.attenuators[ATTENUATOR].set_atten(0)
+                    time.sleep(
+                        10
+                    )  #wait for connection to take place before waiting for scan result
+                elif snumber == 6:
+                    self.log.info(
+                        "Check connection through PNO for reference network")
+                    current_network = self.dut.droid.wifiGetConnectionInfo()
+                    self.log.info("Current network: {}".format(
+                        current_network))
+                    asserts.assert_true('network_id' in current_network,
+                                        NETWORK_ID_ERROR)
+                    asserts.assert_true(current_network['network_id'] >= 0,
+                                        NETWORK_ERROR)
+                    time.sleep(10)  #wait for connection to be active
+                    asserts.assert_true(
+                        wutils.validate_connection(self.dut, self.ping_addr),
+                        "Error, No internet connection for current network")
+                    wutils.wifi_forget_network(self.dut,
+                        self.reference_networks[0]["2g"]["SSID"])
+        except queue.Empty as error:
+            raise AssertionError(
+                "Event did not triggered for batch scan {}".format(error))
+        finally:
+            self.dut.droid.wifiScannerStopBackgroundScan(idx)
+            self.dut.ed.clear_all_events()
+
+    @test_tracker_info(uuid="7c25ce32-0fae-4a68-a7cb-fdf6d4d03caf")
+    def test_wifi_scanner_single_scan_channel_sanity(self):
+        """Test WiFi scanner single scan for mix channel with default setting
+           parameters.
+
+         1. Start WifiScanner single scan for mix channels with default setting
+            parameters.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_setting = {"channels": self.wifi_chs.MIX_CHANNEL_SCAN,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                        wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN}
+        self.wifi_scanner_single_scan(scan_setting)
+
+    @test_tracker_info(uuid="7c8da0c4-dec7-4d04-abd4-f8ea467a5c6d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scanner_dual_radio_low_latency(self):
+        """Test WiFi scanner single scan for mix channel with default setting
+           parameters.
+
+         1. Start WifiScanner single scan for type = SCAN_TYPE_LOW_LATENCY.
+         2. Verify that scan results match with respective scan settings.
+        """
+        if self.dut.model not in self.dbs_supported_models:
+            asserts.skip(
+                ("Device %s does not support dual radio scanning.")
+                % self.dut.model)
+        scan_setting = {"channels": self.wifi_chs.MIX_CHANNEL_SCAN,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT,
+                        "type": wutils.WifiEnums.SCAN_TYPE_LOW_LATENCY}
+        self.wifi_scanner_single_scan_full(scan_setting)
+
+    @test_tracker_info(uuid="58b49b01-851b-4e45-b218-9fd27c0be921")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scanner_dual_radio_low_power(self):
+        """Test WiFi scanner single scan for mix channel with default setting
+           parameters.
+
+         1. Start WifiScanner single scan for type = SCAN_TYPE_LOW_POWER.
+         2. Verify that scan results match with respective scan settings.
+        """
+        if self.dut.model not in self.dbs_supported_models:
+            asserts.skip(
+                ("Device %s does not support dual radio scanning.")
+                % self.dut.model)
+        scan_setting = {"channels": self.wifi_chs.MIX_CHANNEL_SCAN,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT,
+                        "type": wutils.WifiEnums.SCAN_TYPE_LOW_POWER}
+        self.wifi_scanner_single_scan_full(scan_setting)
+
+    @test_tracker_info(uuid="3e7288bc-45e4-497c-bf3a-977eec4e896e")
+    @WifiBaseTest.wifi_test_wrap
+    def test_wifi_scanner_dual_radio_high_accuracy(self):
+        """Test WiFi scanner single scan for mix channel with default setting
+           parameters.
+
+         1. Start WifiScanner single scan for type = SCAN_TYPE_HIGH_ACCURACY.
+         2. Verify that scan results match with respective scan settings.
+        """
+        if self.dut.model not in self.dbs_supported_models:
+            asserts.skip(
+                ("Device %s does not support dual radio scanning.")
+                % self.dut.model)
+        scan_setting = {"channels": self.wifi_chs.MIX_CHANNEL_SCAN,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                            wutils.WifiEnums.REPORT_EVENT_FULL_SCAN_RESULT,
+                        "type": wutils.WifiEnums.SCAN_TYPE_HIGH_ACCURACY}
+        self.wifi_scanner_single_scan_full(scan_setting)
+
+    @test_tracker_info(uuid="e9f3aaad-4af3-4c54-9829-65dc1d6d4987")
+    def test_wifi_scanner_batch_scan_channel_sanity(self):
+        """Test WiFi scanner batch scan for mix channel with default setting
+           parameters to report the result on buffer full.
+
+         1. Start WifiScanner batch scan for mix channels with default setting
+            parameters.
+         2. Verify that scan results match with respective scan settings.
+        """
+        scan_setting = {"channels": self.wifi_chs.MIX_CHANNEL_SCAN,
+                        "periodInMs": SCANTIME,
+                        "reportEvents":
+                        wutils.WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL}
+        self.wifi_scanner_batch_scan(scan_setting)
+
+    @test_tracker_info(uuid="49ba245a-52e2-4c9b-90ad-a2fbc97e3d9f")
+    def test_wifi_scanner_batch_scan_period_too_short(self):
+        """Test WiFi scanner batch scan for band with too short period time.
+
+         1. Start WifiScanner batch scan for both band with interval period as 5s.
+         2. Verify that scan is not started."""
+        scan_setting = {"band": wutils.WifiEnums.WIFI_BAND_BOTH_WITH_DFS,
+                        "periodInMs": 5000,
+                        "reportEvents":
+                        wutils.WifiEnums.REPORT_EVENT_AFTER_BUFFER_FULL}
+        self.start_wifi_scanner_background_scan_expect_failure(scan_setting)
+
+    @test_tracker_info(uuid="6fe45cd7-4fac-4ddd-a950-b9431e68f735")
+    def test_wifi_scanner_single_scan_in_isolated(self):
+        """Test WiFi scanner in isolated environment with default scan settings.
+
+         1. Created isolated environment by attenuating the single by 90db
+         2. Start WifiScanner single scan for mix channels with default setting
+            parameters.
+         3. Verify that empty scan results reported.
+        """
+        self.attenuators[0].set_atten(90)
+        self.attenuators[1].set_atten(90)
+        data = wutils.start_wifi_single_scan(self.dut,
+                                             self.default_scan_setting)
+        idx = data["Index"]
+        scan_rt = data["ScanElapsedRealtime"]
+        self.log.info("Wifi single shot scan started with index: {}".format(
+            idx))
+        results = []
+        #generating event wait time from scan setting plus leeway
+        scan_time, scan_channels = wutils.get_scan_time_and_channels(
+            self.wifi_chs, self.default_scan_setting, self.stime_channel)
+        wait_time = int(scan_time / 1000) + self.leeway
+        try:
+            event_name = "{}{}onResults".format(EVENT_TAG, idx)
+            self.log.debug("Waiting for event: {} for time {}".format(
+                event_name, wait_time))
+            event = self.dut.ed.pop_event(event_name, wait_time)
+            self.log.debug("Event received: {}".format(event))
+            results = event["data"]["Results"]
+            for batch in results:
+                asserts.assert_false(batch["ScanResults"],
+                                     "Test fail because report scan "
+                                     "results reported are not empty")
+        except queue.Empty as error:
+            raise AssertionError(
+                "Event did not triggered for in isolated environment {}".format(
+                    error))
+        finally:
+            self.dut.ed.clear_all_events()
+            self.attenuators[0].set_atten(0)
+            self.attenuators[1].set_atten(0)
+
+    @test_tracker_info(uuid="46f817b9-97a3-455e-af2c-56f9aea64f7e")
+    def test_wifi_scanner_with_wifi_off(self):
+        """Test WiFi scanner single scan when wifi is off.
+
+         1. Toggle wifi state to off.
+         2. Start WifiScanner single scan for both band with default scan settings.
+         3. Verify that scan is not started.
+        """
+        self.log.debug("Make sure wifi is off.")
+        wutils.wifi_toggle_state(self.dut, False)
+        self.start_wifi_scanner_single_scan_expect_failure(
+            self.default_scan_setting)
+        self.log.debug("Turning wifi back on.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+    @test_tracker_info(uuid="257ad734-c21f-49f4-b448-3986b70eba3d")
+    def test_wifi_scanner_with_invalid_numBssidsPerScan(self):
+        """Test WiFi scanner single scan with invalid number of bssids reported
+           per scan.
+
+         1. Start WifiScanner single scan with invalid number of bssids reported
+            per scan.
+         2. Verify that scan results triggered for default supported number of
+            bssids per scan.
+        """
+        scan_setting = {
+            "band": wutils.WifiEnums.WIFI_BAND_BOTH_WITH_DFS,
+            "periodInMs": SCANTIME,
+            "reportEvents": wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN,
+            'numBssidsPerScan': 33
+        }
+        self.wifi_scanner_single_scan(scan_setting)
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiScannerTests.config b/acts_tests/tests/google/wifi/WifiScannerTests.config
new file mode 100755
index 0000000..85d599c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiScannerTests.config
@@ -0,0 +1,28 @@
+{
+  "_description": "Default wireless network setup for APs used in the test.",
+  "AP": [{"index": 0,
+          "radio0": {"settings": {"channel": 1}, "wifi-iface" : [{"ssid": "Test_1", "key": "hahahaha", "encryption": "psk2"},{"ssid": "Test_1.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                 {"ssid": "Test_1.2", "key": "hahahaha", "encryption": "psk2"},{"ssid": "Test_1.3", "key": "hahahaha", "encryption": "psk2"}]},
+          "radio1": {"settings": {"channel": 40}, "wifi-iface" : [{"ssid": "Test_40", "key": "hahahaha", "encryption": "psk2"},{"ssid": "Test_40.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                  {"ssid": "Test_40.2", "key": "hahahaha", "encryption": "psk2"},{"ssid": "Test_40.3", "key": "hahahaha", "encryption": "psk2"}]}
+         },
+         {"index": 1,
+            "radio0": {"settings": {"channel": 6}, "wifi-iface" : [{"ssid": "Test_6", "key": "hahahaha", "encryption": "psk"}, {"ssid": "Test_6.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                   {"ssid": "Test_6.2", "key": "hahahaha", "encryption": "psk2"},  {"ssid": "Test_6.3", "key": "hahahaha", "encryption": "psk2"}]},
+            "radio1": {"settings": {"channel": 40}, "wifi-iface" : [{"ssid": "Test_40", "key": "hahahaha", "encryption": "psk"}, {"ssid": "Test_40.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                    {"ssid": "Test_40.3", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_40.2", "key": "hahahaha", "encryption": "psk2"}]}
+         },
+         {"index": 2,
+            "radio0": {"settings": {"channel": 10}, "wifi-iface" : [{"ssid": "Test_10", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_10.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                    {"ssid": "Test_10.2", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_10.3", "key": "hahahaha", "encryption": "psk2"}]},
+            "radio1": {"settings": {"channel": 44}, "wifi-iface" : [{"ssid": "Test_44", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_44.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                    {"ssid": "Test_44.2", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_44.3", "key": "hahahaha", "encryption": "psk2"}]}
+         },
+         {"index": 3,
+            "radio0": {"settings": {"channel": 11}, "wifi-iface" : [{"ssid": "Test_11", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_11.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                    {"ssid": "Test_11.2", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_11.3", "key": "hahahaha", "encryption": "psk2"}]},
+            "radio1": {"settings": {"channel": 149}, "wifi-iface" : [{"ssid": "Test_149", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_149.1", "key": "hahahaha", "encryption": "psk2"},
+                                                                    {"ssid": "Test_149.2", "key": "hahahaha", "encryption": "psk2"}, {"ssid": "Test_149.3", "key": "hahahaha", "encryption": "psk2"}]}
+         }
+        ]
+}
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiSensitivityTest.py b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
new file mode 100644
index 0000000..7307850
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
@@ -0,0 +1,934 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import csv
+import itertools
+import logging
+import numpy
+import os
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.controllers import iperf_client
+from acts.controllers.utils_lib import ssh
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from functools import partial
+from WifiRvrTest import WifiRvrTest
+from WifiPingTest import WifiPingTest
+
+
+class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
+    """Class to test WiFi sensitivity tests.
+
+    This class implements measures WiFi sensitivity per rate. It heavily
+    leverages the WifiRvrTest class and introduced minor differences to set
+    specific rates and the access point, and implements a different pass/fail
+    check. For an example config file to run this test class see
+    example_connectivity_performance_ap_sta.json.
+    """
+
+    RSSI_POLL_INTERVAL = 0.2
+    VALID_TEST_CONFIGS = {
+        1: ['legacy', 'VHT20'],
+        2: ['legacy', 'VHT20'],
+        6: ['legacy', 'VHT20'],
+        10: ['legacy', 'VHT20'],
+        11: ['legacy', 'VHT20'],
+        36: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
+        40: ['legacy', 'VHT20'],
+        44: ['legacy', 'VHT20'],
+        48: ['legacy', 'VHT20'],
+        149: ['legacy', 'VHT20', 'VHT40', 'VHT80'],
+        153: ['legacy', 'VHT20'],
+        157: ['legacy', 'VHT20'],
+        161: ['legacy', 'VHT20']
+    }
+    RateTuple = collections.namedtuple(('RateTuple'),
+                                       ['mcs', 'streams', 'data_rate'])
+    #yapf:disable
+    VALID_RATES = {
+        'legacy_2GHz': [
+            RateTuple(54, 1, 54), RateTuple(48, 1, 48),
+            RateTuple(36, 1, 36), RateTuple(24, 1, 24),
+            RateTuple(18, 1, 18), RateTuple(12, 1, 12),
+            RateTuple(11, 1, 11), RateTuple(9, 1, 9),
+            RateTuple(6, 1, 6), RateTuple(5.5, 1, 5.5),
+            RateTuple(2, 1, 2), RateTuple(1, 1, 1)],
+        'legacy_5GHz': [
+            RateTuple(54, 1, 54), RateTuple(48, 1, 48),
+            RateTuple(36, 1, 36), RateTuple(24, 1, 24),
+            RateTuple(18, 1, 18), RateTuple(12, 1, 12),
+            RateTuple(9, 1, 9), RateTuple(6, 1, 6)],
+        'HT20': [
+            RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
+            RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
+            RateTuple(3, 1, 26), RateTuple(2, 1, 21.7),
+            RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
+            RateTuple(15, 2, 144.4), RateTuple(14, 2, 130),
+            RateTuple(13, 2, 115.6), RateTuple(12, 2, 86.7),
+            RateTuple(11, 2, 57.8), RateTuple(10, 2, 43.4),
+            RateTuple(9, 2, 28.9), RateTuple(8, 2, 14.4)],
+        'VHT20': [
+            RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
+            RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
+            RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
+            RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
+            RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
+            RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
+            RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
+            RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
+            RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
+            RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
+        'VHT40': [
+            RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
+            RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
+            RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
+            RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
+            RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
+            RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
+            RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
+            RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
+            RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
+            RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
+        'VHT80': [
+            RateTuple(9, 1, 96), RateTuple(8, 1, 86.7),
+            RateTuple(7, 1, 72.2), RateTuple(6, 1, 65),
+            RateTuple(5, 1, 57.8), RateTuple(4, 1, 43.3),
+            RateTuple(3, 1, 28.9), RateTuple(2, 1, 21.7),
+            RateTuple(1, 1, 14.4), RateTuple(0, 1, 7.2),
+            RateTuple(9, 2, 192), RateTuple(8, 2, 173.3),
+            RateTuple(7, 2, 144.4), RateTuple(6, 2, 130.3),
+            RateTuple(5, 2, 115.6), RateTuple(4, 2, 86.7),
+            RateTuple(3, 2, 57.8), RateTuple(2, 2, 43.3),
+            RateTuple(1, 2, 28.9), RateTuple(0, 2, 14.4)],
+    }
+    #yapf:enable
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardwares and compiles parameters that are
+        common to all tests in this class.
+        """
+        self.dut = self.android_devices[-1]
+        req_params = [
+            'RetailAccessPoints', 'sensitivity_test_params', 'testbed_params',
+            'RemoteServer'
+        ]
+        opt_params = ['main_network']
+        self.unpack_userparams(req_params, opt_params)
+        self.testclass_params = self.sensitivity_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.ping_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.iperf_server = self.iperf_servers[0]
+        self.iperf_client = self.iperf_clients[0]
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        self.atten_dut_chain_map = {}
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                                "Can not turn on airplane mode.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+        # Configure test retries
+        self.user_params['retry_tests'] = [self.__class__.__name__]
+
+    def teardown_class(self):
+        # Turn WiFi OFF
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+        self.process_testclass_results()
+
+    def setup_test(self):
+        self.retry_flag = False
+
+    def teardown_test(self):
+        self.retry_flag = False
+
+    def on_retry(self):
+        """Function to control test logic on retried tests.
+
+        This function is automatically executed on tests that are being
+        retried. In this case the function resets wifi, toggles it off and on
+        and sets a retry_flag to enable further tweaking the test logic on
+        second attempts.
+        """
+        self.retry_flag = True
+        for dev in self.android_devices:
+            wutils.reset_wifi(dev)
+            wutils.toggle_wifi_off_and_on(dev)
+
+    def pass_fail_check(self, result):
+        """Checks sensitivity results and decides on pass/fail.
+
+        Args:
+            result: dict containing attenuation, throughput and other meta
+                data
+        """
+        result_string = ('Throughput = {}%, Sensitivity = {}.'.format(
+            result['peak_throughput_pct'], result['sensitivity']))
+        if result['peak_throughput_pct'] < 95:
+            asserts.fail('Result unreliable. {}'.format(result_string))
+        else:
+            asserts.explicit_pass('Test Passed. {}'.format(result_string))
+
+    def process_testclass_results(self):
+        """Saves and plots test results from all executed test cases."""
+        # write json output
+        testclass_results_dict = collections.OrderedDict()
+        id_fields = ['mode', 'rate', 'num_streams', 'chain_mask']
+        channels_tested = []
+        for result in self.testclass_results:
+            testcase_params = result['testcase_params']
+            test_id = self.extract_test_id(testcase_params, id_fields)
+            test_id = tuple(test_id.items())
+            if test_id not in testclass_results_dict:
+                testclass_results_dict[test_id] = collections.OrderedDict()
+            channel = testcase_params['channel']
+            if channel not in channels_tested:
+                channels_tested.append(channel)
+            if result['peak_throughput_pct'] >= 95:
+                testclass_results_dict[test_id][channel] = result[
+                    'sensitivity']
+            else:
+                testclass_results_dict[test_id][channel] = ''
+
+        # calculate average metrics
+        metrics_dict = collections.OrderedDict()
+        id_fields = ['channel', 'mode', 'num_streams', 'chain_mask']
+        for test_id in testclass_results_dict.keys():
+            for channel in testclass_results_dict[test_id].keys():
+                metric_tag = collections.OrderedDict(test_id, channel=channel)
+                metric_tag = self.extract_test_id(metric_tag, id_fields)
+                metric_tag = tuple(metric_tag.items())
+                metrics_dict.setdefault(metric_tag, [])
+                sensitivity_result = testclass_results_dict[test_id][channel]
+                if sensitivity_result != '':
+                    metrics_dict[metric_tag].append(sensitivity_result)
+        for metric_tag_tuple, metric_data in metrics_dict.items():
+            metric_tag_dict = collections.OrderedDict(metric_tag_tuple)
+            metric_tag = 'ch{}_{}_nss{}_chain{}'.format(
+                metric_tag_dict['channel'], metric_tag_dict['mode'],
+                metric_tag_dict['num_streams'], metric_tag_dict['chain_mask'])
+            metric_key = "{}.avg_sensitivity".format(metric_tag)
+            metric_value = numpy.nanmean(metric_data)
+            self.testclass_metric_logger.add_metric(metric_key, metric_value)
+
+        # write csv
+        csv_header = ['Mode', 'MCS', 'Streams', 'Chain', 'Rate (Mbps)']
+        for channel in channels_tested:
+            csv_header.append('Ch. ' + str(channel))
+        results_file_path = os.path.join(self.log_path, 'results.csv')
+        with open(results_file_path, mode='w') as csv_file:
+            writer = csv.DictWriter(csv_file, fieldnames=csv_header)
+            writer.writeheader()
+            for test_id, test_results in testclass_results_dict.items():
+                test_id_dict = dict(test_id)
+                if 'legacy' in test_id_dict['mode']:
+                    rate_list = self.VALID_RATES['legacy_2GHz']
+                else:
+                    rate_list = self.VALID_RATES[test_id_dict['mode']]
+                data_rate = next(rate.data_rate for rate in rate_list
+                                 if rate[:-1] == (test_id_dict['rate'],
+                                                  test_id_dict['num_streams']))
+                row_value = {
+                    'Mode': test_id_dict['mode'],
+                    'MCS': test_id_dict['rate'],
+                    'Streams': test_id_dict['num_streams'],
+                    'Chain': test_id_dict['chain_mask'],
+                    'Rate (Mbps)': data_rate,
+                }
+                for channel in channels_tested:
+                    row_value['Ch. ' + str(channel)] = test_results.pop(
+                        channel, ' ')
+                writer.writerow(row_value)
+
+        if not self.testclass_params['traffic_type'].lower() == 'ping':
+            WifiRvrTest.process_testclass_results(self)
+
+    def process_rvr_test_results(self, testcase_params, rvr_result):
+        """Post processes RvR results to compute sensitivity.
+
+        Takes in the results of the RvR tests and computes the sensitivity of
+        the current rate by looking at the point at which throughput drops
+        below the percentage specified in the config file. The function then
+        calls on its parent class process_test_results to plot the result.
+
+        Args:
+            rvr_result: dict containing attenuation, throughput and other meta
+            data
+        """
+        rvr_result['peak_throughput'] = max(rvr_result['throughput_receive'])
+        rvr_result['peak_throughput_pct'] = 100
+        throughput_check = [
+            throughput < rvr_result['peak_throughput'] *
+            (self.testclass_params['throughput_pct_at_sensitivity'] / 100)
+            for throughput in rvr_result['throughput_receive']
+        ]
+        consistency_check = [
+            idx for idx in range(len(throughput_check))
+            if all(throughput_check[idx:])
+        ]
+        rvr_result['atten_at_range'] = rvr_result['attenuation'][
+            consistency_check[0] - 1]
+        rvr_result['range'] = rvr_result['fixed_attenuation'] + (
+            rvr_result['atten_at_range'])
+        rvr_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
+            self.testbed_params['ap_tx_power_offset'][str(
+                testcase_params['channel'])] - rvr_result['range'])
+        WifiRvrTest.process_test_results(self, rvr_result)
+
+    def process_ping_test_results(self, testcase_params, ping_result):
+        """Post processes RvR results to compute sensitivity.
+
+        Takes in the results of the RvR tests and computes the sensitivity of
+        the current rate by looking at the point at which throughput drops
+        below the percentage specified in the config file. The function then
+        calls on its parent class process_test_results to plot the result.
+
+        Args:
+            rvr_result: dict containing attenuation, throughput and other meta
+            data
+        """
+        WifiPingTest.process_ping_results(self, testcase_params, ping_result)
+        ping_result['sensitivity'] = self.testclass_params['ap_tx_power'] + (
+            self.testbed_params['ap_tx_power_offset'][str(
+                testcase_params['channel'])] - ping_result['range'])
+
+    def setup_sensitivity_test(self, testcase_params):
+        if testcase_params['traffic_type'].lower() == 'ping':
+            self.setup_ping_test(testcase_params)
+            self.run_sensitivity_test = self.run_ping_test
+            self.process_sensitivity_test_results = (
+                self.process_ping_test_results)
+        else:
+            self.setup_rvr_test(testcase_params)
+            self.run_sensitivity_test = self.run_rvr_test
+            self.process_sensitivity_test_results = (
+                self.process_rvr_test_results)
+
+    def setup_ap(self, testcase_params):
+        """Sets up the AP and attenuator to compensate for AP chain imbalance.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '2G' in band:
+            frequency = wutils.WifiEnums.channel_2G_to_freq[
+                testcase_params['channel']]
+        else:
+            frequency = wutils.WifiEnums.channel_5G_to_freq[
+                testcase_params['channel']]
+        if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
+            self.access_point.set_region(self.testbed_params['DFS_region'])
+        else:
+            self.access_point.set_region(self.testbed_params['default_region'])
+        self.access_point.set_channel(band, testcase_params['channel'])
+        self.access_point.set_bandwidth(band, testcase_params['mode'])
+        self.access_point.set_power(band, testcase_params['ap_tx_power'])
+        self.access_point.set_rate(band, testcase_params['mode'],
+                                   testcase_params['num_streams'],
+                                   testcase_params['rate'],
+                                   testcase_params['short_gi'])
+        # Set attenuator offsets and set attenuators to initial condition
+        atten_offsets = self.testbed_params['chain_offset'][str(
+            testcase_params['channel'])]
+        for atten in self.attenuators:
+            if 'AP-Chain-0' in atten.path:
+                atten.offset = atten_offsets[0]
+            elif 'AP-Chain-1' in atten.path:
+                atten.offset = atten_offsets[1]
+            else:
+                atten.offset = 0
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('Battery level too low. Skipping test.')
+        # Turn screen off to preserve battery
+        self.dut.go_to_sleep()
+        if wputils.validate_network(self.dut,
+                                    testcase_params['test_network']['SSID']):
+            self.log.info('Already connected to desired network')
+        else:
+            wutils.reset_wifi(self.dut)
+            wutils.set_wifi_country_code(self.dut,
+                                         self.testclass_params['country_code'])
+            testcase_params['test_network']['channel'] = testcase_params[
+                'channel']
+            wutils.wifi_connect(self.dut,
+                                testcase_params['test_network'],
+                                num_of_tries=5,
+                                check_connectivity=False)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        # Activate/attenuate the correct chains
+        if testcase_params['channel'] not in self.atten_dut_chain_map.keys():
+            self.atten_dut_chain_map[testcase_params[
+                'channel']] = wputils.get_current_atten_dut_chain_map(
+                    self.attenuators, self.dut, self.ping_server)
+        self.log.info("Current Attenuator-DUT Chain Map: {}".format(
+            self.atten_dut_chain_map[testcase_params['channel']]))
+        for idx, atten in enumerate(self.attenuators):
+            if self.atten_dut_chain_map[testcase_params['channel']][
+                    idx] == testcase_params['attenuated_chain']:
+                atten.offset = atten.instrument.max_atten
+
+    def extract_test_id(self, testcase_params, id_fields):
+        test_id = collections.OrderedDict(
+            (param, testcase_params[param]) for param in id_fields)
+        return test_id
+
+    def get_start_atten(self, testcase_params):
+        """Gets the starting attenuation for this sensitivity test.
+
+        The function gets the starting attenuation by checking whether a test
+        as the next higher MCS has been executed. If so it sets the starting
+        point a configurable number of dBs below the next MCS's sensitivity.
+
+        Returns:
+            start_atten: starting attenuation for current test
+        """
+        # If the test is being retried, start from the beginning
+        if self.retry_flag:
+            self.log.info('Retry flag set. Setting attenuation to minimum.')
+            return self.testclass_params['atten_start']
+        # Get the current and reference test config. The reference test is the
+        # one performed at the current MCS+1
+        current_rate = testcase_params['rate']
+        ref_test_params = self.extract_test_id(
+            testcase_params,
+            ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
+        if 'legacy' in testcase_params['mode']:
+            if testcase_params['channel'] <= 13:
+                rate_list = self.VALID_RATES['legacy_2GHz']
+            else:
+                rate_list = self.VALID_RATES['legacy_5GHz']
+            ref_index = max(
+                0,
+                rate_list.index(self.RateTuple(current_rate, 1, current_rate))
+                - 1)
+            ref_test_params['rate'] = rate_list[ref_index].mcs
+        else:
+            ref_test_params['rate'] = current_rate + 1
+
+        # Check if reference test has been run and set attenuation accordingly
+        previous_params = [
+            self.extract_test_id(
+                result['testcase_params'],
+                ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
+            for result in self.testclass_results
+        ]
+
+        try:
+            ref_index = previous_params.index(ref_test_params)
+            start_atten = self.testclass_results[ref_index][
+                'atten_at_range'] - (
+                    self.testclass_params['adjacent_mcs_range_gap'])
+        except ValueError:
+            self.log.warning(
+                'Reference test not found. Starting from {} dB'.format(
+                    self.testclass_params['atten_start']))
+            start_atten = self.testclass_params['atten_start']
+            start_atten = max(start_atten, 0)
+        return start_atten
+
+    def compile_test_params(self, testcase_params):
+        """Function that generates test params based on the test name."""
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[band]
+        if testcase_params['chain_mask'] in ['0', '1']:
+            testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
+                1 if testcase_params['chain_mask'] == '0' else 0)
+        else:
+            # Set attenuated chain to -1. Do not set to None as this will be
+            # compared to RF chain map which may include None
+            testcase_params['attenuated_chain'] = -1
+
+        self.testclass_params[
+            'range_ping_loss_threshold'] = 100 - self.testclass_params[
+                'throughput_pct_at_sensitivity']
+        if self.testclass_params['traffic_type'] == 'UDP':
+            testcase_params['iperf_args'] = '-i 1 -t {} -J -u -b {}'.format(
+                self.testclass_params['iperf_duration'],
+                self.testclass_params['UDP_rates'][testcase_params['mode']])
+        elif self.testclass_params['traffic_type'] == 'TCP':
+            testcase_params['iperf_args'] = '-i 1 -t {} -J'.format(
+                self.testclass_params['iperf_duration'])
+
+        if self.testclass_params['traffic_type'] != 'ping' and isinstance(
+                self.iperf_client, iperf_client.IPerfClientOverAdb):
+            testcase_params['iperf_args'] += ' -R'
+            testcase_params['use_client_output'] = True
+        else:
+            testcase_params['use_client_output'] = False
+
+        return testcase_params
+
+    def _test_sensitivity(self, testcase_params):
+        """ Function that gets called for each test case
+
+        The function gets called in each rvr test case. The function customizes
+        the rvr test based on the test name of the test that called it
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+        testcase_params.update(self.testclass_params)
+        testcase_params['atten_start'] = self.get_start_atten(testcase_params)
+        num_atten_steps = int(
+            (testcase_params['atten_stop'] - testcase_params['atten_start']) /
+            testcase_params['atten_step'])
+        testcase_params['atten_range'] = [
+            testcase_params['atten_start'] + x * testcase_params['atten_step']
+            for x in range(0, num_atten_steps)
+        ]
+
+        # Prepare devices and run test
+        self.setup_sensitivity_test(testcase_params)
+        result = self.run_sensitivity_test(testcase_params)
+        self.process_sensitivity_test_results(testcase_params, result)
+
+        # Post-process results
+        self.testclass_results.append(result)
+        self.pass_fail_check(result)
+
+    def generate_test_cases(self, channels, modes, chain_mask):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+        for channel in channels:
+            requested_modes = [
+                mode for mode in modes
+                if mode in self.VALID_TEST_CONFIGS[channel]
+            ]
+            for mode in requested_modes:
+                if 'VHT' in mode:
+                    rates = self.VALID_RATES[mode]
+                elif 'HT' in mode:
+                    rates = self.VALID_RATES[mode]
+                elif 'legacy' in mode and channel < 14:
+                    rates = self.VALID_RATES['legacy_2GHz']
+                elif 'legacy' in mode and channel > 14:
+                    rates = self.VALID_RATES['legacy_5GHz']
+                else:
+                    raise ValueError('Invalid test mode.')
+                for chain, rate in itertools.product(chain_mask, rates):
+                    testcase_params = collections.OrderedDict(
+                        channel=channel,
+                        mode=mode,
+                        rate=rate.mcs,
+                        num_streams=rate.streams,
+                        short_gi=1,
+                        chain_mask=chain)
+                    if chain in ['0', '1'] and rate[1] == 2:
+                        # Do not test 2-stream rates in single chain mode
+                        continue
+                    if 'legacy' in mode:
+                        testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
+                                         '_ch{}'.format(
+                                             channel, mode,
+                                             str(rate.mcs).replace('.', 'p'),
+                                             rate.streams, chain))
+                    else:
+                        testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
+                                         '_ch{}'.format(
+                                             channel, mode, rate.mcs,
+                                             rate.streams, chain))
+                    setattr(self, testcase_name,
+                            partial(self._test_sensitivity, testcase_params))
+                    test_cases.append(testcase_name)
+        return test_cases
+
+
+class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            [6, 36, 40, 44, 48, 149, 153, 157, 161],
+            ['VHT20', 'VHT40', 'VHT80'], ['0', '1', '2x2'])
+
+
+class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases([6, 36, 149],
+                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['0', '1', '2x2'])
+
+
+class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
+                                              ['0', '1', '2x2'])
+
+
+class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases(
+            [36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT40', 'VHT80'],
+            ['0', '1', '2x2'])
+
+
+class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases([36, 40, 44, 48],
+                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['0', '1', '2x2'])
+
+
+class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.tests = self.generate_test_cases([149, 153, 157, 161],
+                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['0', '1', '2x2'])
+
+
+# Over-the air version of senstivity tests
+class WifiOtaSensitivityTest(WifiSensitivityTest):
+    """Class to test over-the-air senstivity.
+
+    This class implements measures WiFi sensitivity tests in an OTA chamber.
+    It allows setting orientation and other chamber parameters to study
+    performance in varying channel conditions
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = False
+
+    def setup_class(self):
+        WifiSensitivityTest.setup_class(self)
+        self.current_chain_mask = '2x2'
+        self.ota_chamber = ota_chamber.create(
+            self.user_params['OTAChamber'])[0]
+
+    def teardown_class(self):
+        WifiSensitivityTest.teardown_class(self)
+        self.ota_chamber.reset_chamber()
+
+    def setup_sensitivity_test(self, testcase_params):
+        # Setup turntable
+        self.ota_chamber.set_orientation(testcase_params['orientation'])
+        # Continue test setup
+        WifiSensitivityTest.setup_sensitivity_test(self, testcase_params)
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Configure the right INI settings
+        if testcase_params['chain_mask'] != self.current_chain_mask:
+            self.log.info('Updating WiFi chain mask to: {}'.format(
+                testcase_params['chain_mask']))
+            self.current_chain_mask = testcase_params['chain_mask']
+            if testcase_params['chain_mask'] in ['0', '1']:
+                wputils.set_ini_single_chain_mode(
+                    self.dut, int(testcase_params['chain_mask']))
+            else:
+                wputils.set_ini_two_chain_mode(self.dut)
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('Battery level too low. Skipping test.')
+        # Turn screen off to preserve battery
+        self.dut.go_to_sleep()
+        if wputils.validate_network(self.dut,
+                                    testcase_params['test_network']['SSID']):
+            self.log.info('Already connected to desired network')
+        else:
+            wutils.reset_wifi(self.dut)
+            wutils.set_wifi_country_code(self.dut,
+                                         self.testclass_params['country_code'])
+            testcase_params['test_network']['channel'] = testcase_params[
+                'channel']
+            wutils.wifi_connect(self.dut,
+                                testcase_params['test_network'],
+                                num_of_tries=5,
+                                check_connectivity=False)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+    def process_testclass_results(self):
+        """Saves and plots test results from all executed test cases."""
+        testclass_results_dict = collections.OrderedDict()
+        id_fields = ['channel', 'mode', 'rate']
+        plots = []
+        for result in self.testclass_results:
+            test_id = self.extract_test_id(result['testcase_params'],
+                                           id_fields)
+            test_id = tuple(test_id.items())
+            chain_mask = result['testcase_params']['chain_mask']
+            num_streams = result['testcase_params']['num_streams']
+            line_id = (chain_mask, num_streams)
+            if test_id not in testclass_results_dict:
+                testclass_results_dict[test_id] = collections.OrderedDict()
+            if line_id not in testclass_results_dict[test_id]:
+                testclass_results_dict[test_id][line_id] = {
+                    'orientation': [],
+                    'sensitivity': []
+                }
+            orientation = result['testcase_params']['orientation']
+            if result['peak_throughput_pct'] >= 95:
+                sensitivity = result['sensitivity']
+            else:
+                sensitivity = float('nan')
+            if orientation not in testclass_results_dict[test_id][line_id][
+                    'orientation']:
+                testclass_results_dict[test_id][line_id]['orientation'].append(
+                    orientation)
+                testclass_results_dict[test_id][line_id]['sensitivity'].append(
+                    sensitivity)
+            else:
+                testclass_results_dict[test_id][line_id]['sensitivity'][
+                    -1] = sensitivity
+
+        for test_id, test_data in testclass_results_dict.items():
+            test_id_dict = dict(test_id)
+            if 'legacy' in test_id_dict['mode']:
+                test_id_str = 'Channel {} - {} {}Mbps'.format(
+                    test_id_dict['channel'], test_id_dict['mode'],
+                    test_id_dict['rate'])
+            else:
+                test_id_str = 'Channel {} - {} MCS{}'.format(
+                    test_id_dict['channel'], test_id_dict['mode'],
+                    test_id_dict['rate'])
+            curr_plot = wputils.BokehFigure(
+                title=str(test_id_str),
+                x_label='Orientation (deg)',
+                primary_y_label='Sensitivity (dBm)')
+            for line_id, line_results in test_data.items():
+                curr_plot.add_line(line_results['orientation'],
+                                   line_results['sensitivity'],
+                                   legend='Nss{} - Chain Mask {}'.format(
+                                       line_id[1], line_id[0]),
+                                   marker='circle')
+                if 'legacy' in test_id_dict['mode']:
+                    metric_tag = 'ota_summary_ch{}_{}_{}_ch{}'.format(
+                        test_id_dict['channel'], test_id_dict['mode'],
+                        test_id_dict['rate'], line_id[0])
+                else:
+                    metric_tag = 'ota_summary_ch{}_{}_mcs{}_nss{}_ch{}'.format(
+                        test_id_dict['channel'], test_id_dict['mode'],
+                        test_id_dict['rate'], line_id[1], line_id[0])
+
+                metric_name = metric_tag + '.avg_sensitivity'
+                metric_value = numpy.nanmean(line_results['sensitivity'])
+                self.testclass_metric_logger.add_metric(
+                    metric_name, metric_value)
+                self.log.info(("Average Sensitivity for {}: {:.1f}").format(
+                    metric_tag, metric_value))
+            current_context = (
+                context.get_current_context().get_full_output_path())
+            output_file_path = os.path.join(current_context,
+                                            str(test_id_str) + '.html')
+            curr_plot.generate_figure(output_file_path)
+            plots.append(curr_plot)
+        output_file_path = os.path.join(current_context, 'results.html')
+        wputils.BokehFigure.save_figures(plots, output_file_path)
+
+    def get_start_atten(self, testcase_params):
+        """Gets the starting attenuation for this sensitivity test.
+
+        The function gets the starting attenuation by checking whether a test
+        at the same rate configuration has executed. If so it sets the starting
+        point a configurable number of dBs below the reference test.
+
+        Returns:
+            start_atten: starting attenuation for current test
+        """
+        # If the test is being retried, start from the beginning
+        if self.retry_flag:
+            self.log.info('Retry flag set. Setting attenuation to minimum.')
+            return self.testclass_params['atten_start']
+        # Get the current and reference test config. The reference test is the
+        # one performed at the current MCS+1
+        ref_test_params = self.extract_test_id(
+            testcase_params,
+            ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
+        # Check if reference test has been run and set attenuation accordingly
+        previous_params = [
+            self.extract_test_id(
+                result['testcase_params'],
+                ['channel', 'mode', 'rate', 'num_streams', 'chain_mask'])
+            for result in self.testclass_results
+        ]
+        try:
+            ref_index = previous_params[::-1].index(ref_test_params)
+            ref_index = len(previous_params) - 1 - ref_index
+            start_atten = self.testclass_results[ref_index][
+                'atten_at_range'] - (
+                    self.testclass_params['adjacent_mcs_range_gap'])
+        except ValueError:
+            print('Reference test not found. Starting from {} dB'.format(
+                self.testclass_params['atten_start']))
+            start_atten = self.testclass_params['atten_start']
+        start_atten = max(start_atten, 0)
+        return start_atten
+
+    def generate_test_cases(self, channels, modes, requested_rates, chain_mask,
+                            angles):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+        for channel in channels:
+            requested_modes = [
+                mode for mode in modes
+                if mode in self.VALID_TEST_CONFIGS[channel]
+            ]
+            for chain, mode in itertools.product(chain_mask, requested_modes):
+                if 'VHT' in mode:
+                    valid_rates = self.VALID_RATES[mode]
+                elif 'HT' in mode:
+                    valid_rates = self.VALID_RATES[mode]
+                elif 'legacy' in mode and channel < 14:
+                    valid_rates = self.VALID_RATES['legacy_2GHz']
+                elif 'legacy' in mode and channel > 14:
+                    valid_rates = self.VALID_RATES['legacy_5GHz']
+                else:
+                    raise ValueError('Invalid test mode.')
+                for rate, angle in itertools.product(valid_rates, angles):
+                    testcase_params = collections.OrderedDict(
+                        channel=channel,
+                        mode=mode,
+                        rate=rate.mcs,
+                        num_streams=rate.streams,
+                        short_gi=1,
+                        chain_mask=chain,
+                        orientation=angle)
+                    if rate not in requested_rates:
+                        continue
+                    if str(chain) in ['0', '1'] and rate[1] == 2:
+                        # Do not test 2-stream rates in single chain mode
+                        continue
+                    if 'legacy' in mode:
+                        testcase_name = ('test_sensitivity_ch{}_{}_{}_nss{}'
+                                         '_ch{}_{}deg'.format(
+                                             channel, mode,
+                                             str(rate.mcs).replace('.', 'p'),
+                                             rate.streams, chain, angle))
+                    else:
+                        testcase_name = ('test_sensitivity_ch{}_{}_mcs{}_nss{}'
+                                         '_ch{}_{}deg'.format(
+                                             channel, mode, rate.mcs,
+                                             rate.streams, chain, angle))
+                    setattr(self, testcase_name,
+                            partial(self._test_sensitivity, testcase_params))
+                    test_cases.append(testcase_name)
+        return test_cases
+
+
+class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
+    def __init__(self, controllers):
+        WifiOtaSensitivityTest.__init__(self, controllers)
+        requested_channels = [6, 36, 149]
+        requested_rates = [
+            self.RateTuple(8, 1, 86.7),
+            self.RateTuple(2, 1, 21.7),
+            self.RateTuple(8, 2, 173.3),
+            self.RateTuple(2, 2, 43.3)
+        ]
+        self.tests = self.generate_test_cases(requested_channels,
+                                              ['VHT20', 'VHT80'],
+                                              requested_rates, ['2x2'],
+                                              list(range(0, 360, 10)))
+
+
+class WifiOtaSensitivity_PerChain_TenDegree_Test(WifiOtaSensitivityTest):
+    def __init__(self, controllers):
+        WifiOtaSensitivityTest.__init__(self, controllers)
+        requested_channels = [6, 36, 149]
+        requested_rates = [
+            self.RateTuple(2, 1, 21.7),
+            self.RateTuple(2, 2, 43.3)
+        ]
+        self.tests = self.generate_test_cases(requested_channels, ['VHT20'],
+                                              requested_rates,
+                                              ['0', '1', '2x2'],
+                                              list(range(0, 360, 10)))
+
+
+class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
+    def __init__(self, controllers):
+        WifiOtaSensitivityTest.__init__(self, controllers)
+        requested_channels = [6, 36, 149]
+        requested_rates = [
+            self.RateTuple(9, 1, 96),
+            self.RateTuple(8, 1, 86.7),
+            self.RateTuple(7, 1, 72.2),
+            self.RateTuple(4, 1, 43.3),
+            self.RateTuple(2, 1, 21.7),
+            self.RateTuple(0, 1, 7.2),
+            self.RateTuple(9, 2, 192),
+            self.RateTuple(8, 2, 173.3),
+            self.RateTuple(7, 2, 144.4),
+            self.RateTuple(4, 2, 86.7),
+            self.RateTuple(2, 2, 43.3),
+            self.RateTuple(0, 2, 14.4)
+        ]
+        self.tests = self.generate_test_cases(requested_channels,
+                                              ['VHT20', 'VHT80'],
+                                              requested_rates, ['2x2'],
+                                              list(range(0, 360, 30)))
+
+
+class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
+    def __init__(self, controllers):
+        WifiOtaSensitivityTest.__init__(self, controllers)
+        requested_rates = [
+            self.RateTuple(8, 1, 86.7),
+            self.RateTuple(2, 1, 21.7),
+            self.RateTuple(8, 2, 173.3),
+            self.RateTuple(2, 2, 43.3)
+        ]
+        self.tests = self.generate_test_cases(
+            [1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161], ['VHT20', 'VHT80'],
+            requested_rates, ['2x2'], list(range(0, 360, 45)))
diff --git a/acts_tests/tests/google/wifi/WifiServiceApiTest.py b/acts_tests/tests/google/wifi/WifiServiceApiTest.py
new file mode 100644
index 0000000..afe2a84
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiServiceApiTest.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import queue
+import sys
+import time
+
+from acts import signals
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_constants
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class WifiServiceApiTest(WifiBaseTest):
+    """This class tests the API surface of WifiManager in different wifi states.
+
+       Attributes:
+       The tests in this class only require one DUT.
+       The tests in this class do not require a SIM (but it is ok if one is
+           present).
+    """
+
+
+    TEST_SSID_PREFIX = "test_config_"
+    CONFIG_ELEMENT = 'config'
+    NETWORK_ID_ELEMENT = 'network_id'
+
+    def setup_class(self):
+        """ Sets up the required dependencies from the config file and
+            configures the device for WifiService API tests.
+
+            Returns:
+            True is successfully configured the requirements for testig.
+        """
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        # Do a simple version of init - mainly just sync the time and enable
+        # verbose logging.  We would also like to test with phones in less
+        # constrained states (or add variations where we specifically
+        # constrain).
+        utils.require_sl4a((self.dut, ))
+        utils.sync_device_time(self.dut)
+
+        # Enable verbose logging on the dut
+        self.dut.droid.wifiEnableVerboseLogging(1)
+        if self.dut.droid.wifiGetVerboseLoggingLevel() != 1:
+            raise signals.TestFailure(
+                    "Failed to enable WiFi verbose logging on the dut.")
+
+    def teardown_class(self):
+        wutils.reset_wifi(self.dut)
+
+    def create_and_save_wifi_network_config(self):
+        """ Create a config with random SSID and password.
+
+            Returns:
+            A tuple with the config and networkId for the newly created and saved network.
+        """
+        config_ssid = self.TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+        config_password = utils.rand_ascii_str(8)
+        self.dut.log.info("creating config: %s %s", config_ssid, config_password)
+        config = {wutils.WifiEnums.SSID_KEY: config_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = config_password
+
+        # Now save the config.
+        network_id = self.dut.droid.wifiAddNetwork(config)
+        self.dut.log.info("saved config: network_id = %s", network_id)
+        return {self.NETWORK_ID_ELEMENT: network_id, self.CONFIG_ELEMENT: config}
+
+    def check_network_config_saved(self, config):
+        """ Get the configured networks and check of the provided config
+            is present.  This method only checks if the SSID is the same.
+            TODO: should do a deeper check to make sure this is the
+            correct config.
+
+            Args:
+                config: WifiConfig for a network.
+
+            Returns:
+                True if the WifiConfig is present.
+        """
+        networks = self.dut.droid.wifiGetConfiguredNetworks()
+        if not networks:
+            return False
+        ssid_key = wutils.WifiEnums.SSID_KEY
+        for network in networks:
+            if config[ssid_key] == network[ssid_key]:
+                return True
+        return False
+
+    def forget_network(self, network_id):
+        """ Simple method to call wifiForgetNetwork and wait for confirmation
+            callback.  The method returns False if it was not removed.
+
+            Returns:
+                True if network was successfully deleted.
+        """
+        self.dut.log.info("deleting config: networkId = %s", network_id)
+        self.dut.droid.wifiForgetNetwork(network_id)
+        try:
+            event = self.dut.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS, 10)
+            return True
+        except queue.Empty:
+            self.dut.log.error("Failed to forget network")
+            return False
+
+
+    """ Tests Begin """
+    @test_tracker_info(uuid="f4df08c2-d3d5-4032-a433-c15f55130d4a")
+    def test_remove_config_wifi_enabled(self):
+        """ Test if config can be deleted when wifi is enabled.
+
+            1. Enable wifi, if needed
+            2. Create and save a random config.
+            3. Confirm the config is present.
+            4. Remove the config.
+            5. Confirm the config is not listed.
+        """
+        wutils.wifi_toggle_state(self.dut, True)
+        test_network = self.create_and_save_wifi_network_config()
+        if not self.check_network_config_saved(test_network[self.CONFIG_ELEMENT]):
+            raise signals.TestFailure(
+                    "Test network not found in list of configured networks.")
+        if not self.forget_network(test_network[self.NETWORK_ID_ELEMENT]):
+            raise signals.TestFailure(
+                    "Test network not deleted from configured networks.")
+        if self.check_network_config_saved(test_network[self.CONFIG_ELEMENT]):
+            raise signals.TestFailure(
+                    "Deleted network was in configured networks list.")
+
+    @test_tracker_info(uuid="9af96c7d-a316-4d57-ba5f-c992427c237b")
+    def test_remove_config_wifi_disabled(self):
+        """ Test if config can be deleted when wifi is disabled.
+
+            1. Enable wifi, if needed
+            2. Create and save a random config.
+            3. Confirm the config is present.
+            4. Disable wifi.
+            5. Remove the config.
+            6. Confirm the config is not listed.
+        """
+        wutils.wifi_toggle_state(self.dut, True)
+        test_network = self.create_and_save_wifi_network_config()
+        if not self.check_network_config_saved(test_network[self.CONFIG_ELEMENT]):
+            raise signals.TestFailure(
+                    "Test network not found in list of configured networks.")
+        wutils.wifi_toggle_state(self.dut, False)
+        if not self.forget_network(test_network[self.NETWORK_ID_ELEMENT]):
+            raise signals.TestFailure("Failed to delete network.")
+        if self.check_network_config_saved(test_network[self.CONFIG_ELEMENT]):
+            raise signals.TestFailure(
+                    "Test network was found in list of configured networks.")
+
+    @test_tracker_info(uuid="79204ae6-323b-4257-a2cb-2225d44199d4")
+    def test_retrieve_config_wifi_enabled(self):
+        """ Test if config can be retrieved when wifi is enabled.
+
+            1. Enable wifi
+            2. Create and save a random config
+            3. Retrieve the config
+            4. Remove the config (clean up from the test)
+        """
+        wutils.wifi_toggle_state(self.dut, True)
+        test_network = self.create_and_save_wifi_network_config()
+
+        if not self.check_network_config_saved(test_network[self.CONFIG_ELEMENT]):
+            raise signals.TestFailure(
+                    "Test network not found in list of configured networks.")
+        if not self.forget_network(test_network[self.NETWORK_ID_ELEMENT]):
+            raise signals.TestFailure("Failed to delete network.")
+
+    @test_tracker_info(uuid="58fb4f81-bc19-43e1-b0af-89dbd17f45b2")
+    def test_retrieve_config_wifi_disabled(self):
+        """ Test if config can be retrieved when wifi is disabled.
+
+            1. Disable wifi
+            2. Create and save a random config
+            3. Retrieve the config
+            4. Remove the config (clean up from the test)
+        """
+        wutils.wifi_toggle_state(self.dut, False)
+        test_network = self.create_and_save_wifi_network_config()
+        if not self.check_network_config_saved(test_network[self.CONFIG_ELEMENT]):
+            raise signals.TestFailure(
+                    "Test network not found in list of configured networks.")
+        if not self.forget_network(test_network[self.NETWORK_ID_ELEMENT]):
+            raise signals.TestFailure("Failed to delete network.")
+
+    """ Tests End """
+
+
+if __name__ == "__main__":
+      pass
diff --git a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
new file mode 100644
index 0000000..a3cbdfe
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
@@ -0,0 +1,616 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import itertools
+import pprint
+import queue
+import sys
+import time
+
+import acts.base_test
+import acts.signals as signals
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils as utils
+
+from acts import asserts
+from acts.controllers.ap_lib import hostapd_constants
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from threading import Thread
+
+WifiEnums = wutils.WifiEnums
+WIFI_CONFIG_APBAND_AUTO = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G
+GET_FREQUENCY_NUM_RETRIES = 3
+
+class WifiSoftApAcsTest(WifiBaseTest):
+    """Tests for Automatic Channel Selection.
+
+    Test Bed Requirement:
+    * Two Android devices and an AP.
+    * 2GHz and 5GHz  Wi-Fi network visible to the device.
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+        wutils.wifi_test_device_init(self.dut)
+        wutils.wifi_test_device_init(self.dut_client)
+        utils.require_sl4a((self.dut, self.dut_client))
+        utils.sync_device_time(self.dut)
+        utils.sync_device_time(self.dut_client)
+        # Set country code explicitly to "US".
+        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
+        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
+        # Enable verbose logging on the duts
+        self.dut.droid.wifiEnableVerboseLogging(1)
+        asserts.assert_equal(self.dut.droid.wifiGetVerboseLoggingLevel(), 1,
+            "Failed to enable WiFi verbose logging on the softap dut.")
+        self.dut_client.droid.wifiEnableVerboseLogging(1)
+        asserts.assert_equal(self.dut_client.droid.wifiGetVerboseLoggingLevel(), 1,
+            "Failed to enable WiFi verbose logging on the client dut.")
+        req_params = []
+        opt_param = ["iperf_server_address", "reference_networks",
+                     "iperf_server_port", "pixel_models"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+        self.chan_map = {v: k for k, v in hostapd_constants.CHANNEL_MAP.items()}
+        self.pcap_procs = None
+
+    def setup_test(self):
+        super().setup_test()
+        if hasattr(self, 'packet_capture'):
+            chan = self.test_name.split('_')[-1]
+            if chan.isnumeric():
+                band = '2G' if self.chan_map[int(chan)] < 5000 else '5G'
+                self.packet_capture[0].configure_monitor_mode(band, int(chan))
+                self.pcap_procs = wutils.start_pcap(
+                    self.packet_capture[0], band, self.test_name)
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.stop_wifi_tethering(self.dut)
+        wutils.reset_wifi(self.dut)
+        wutils.reset_wifi(self.dut_client)
+        if hasattr(self, 'packet_capture') and self.pcap_procs:
+            wutils.stop_pcap(self.packet_capture[0], self.pcap_procs, False)
+            self.pcap_procs = None
+        try:
+            if "AccessPoint" in self.user_params:
+                del self.user_params["reference_networks"]
+                del self.user_params["open_network"]
+        except:
+            pass
+        self.access_points[0].close()
+
+    """Helper Functions"""
+
+    def run_iperf_client(self, params):
+        """Run iperf traffic after connection.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+
+        """
+        if "iperf_server_address" in self.user_params:
+            network, ad = params
+            SSID = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic through {}".format(SSID))
+            port_arg = "-p {} -t {}".format(self.iperf_server_port, 3)
+            success, data = ad.run_iperf_client(self.iperf_server_address,
+                                                port_arg)
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+            self.log.info("Finished iperf traffic through {}".format(SSID))
+
+    def start_softap_and_verify(self, band):
+        """Bring-up softap and verify AP mode and in scan results.
+
+        Args:
+            band: The band to use for softAP.
+
+        """
+        config = wutils.create_softap_config()
+        wutils.start_wifi_tethering(self.dut,
+                                    config[wutils.WifiEnums.SSID_KEY],
+                                    config[wutils.WifiEnums.PWD_KEY], band=band)
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                             "SoftAp is not reported as running")
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut_client, config[wutils.WifiEnums.SSID_KEY])
+        return config
+
+    def get_softap_acs(self, softap):
+        """Connect to the softap on client-dut and get the softap channel
+           information.
+
+        Args:
+            softap: The softap network configuration information.
+
+        """
+        wutils.connect_to_wifi_network(self.dut_client, softap,
+            check_connectivity=False)
+        for _ in range(GET_FREQUENCY_NUM_RETRIES):
+            softap_info = self.dut_client.droid.wifiGetConnectionInfo()
+            self.log.debug("DUT is connected to softAP %s with details: %s" %
+                           (softap[wutils.WifiEnums.SSID_KEY], softap_info))
+            frequency = softap_info['frequency']
+            if frequency > 0:
+                break
+            time.sleep(1) # frequency not updated yet, try again after a delay
+
+        return hostapd_constants.CHANNEL_MAP[frequency]
+
+    def configure_ap(self, channel_2g=None, channel_5g=None):
+        """Configure and bring up AP on required channel.
+
+        Args:
+            channel_2g: The channel number to use for 2GHz network.
+            channel_5g: The channel number to use for 5GHz network.
+
+        """
+        if not channel_2g:
+            channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        if not channel_5g:
+            channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(wpa_network=True,
+                                               wep_network=True,
+                                               channel_2g=channel_2g,
+                                               channel_5g=channel_5g)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                wep_network=True,
+                                                channel_2g=channel_2g,
+                                                channel_5g=channel_5g)
+
+    def start_traffic_and_softap(self, network, softap_band):
+        """Start iPerf traffic on client dut, during softAP bring-up on dut.
+
+        Args:
+            network: Network information of the network to connect to.
+            softap_band: The band to use for softAP.
+
+        """
+        if not network:
+            # For a clean environment just bring up softap and return channel.
+            softap = self.start_softap_and_verify(softap_band)
+            channel = self.get_softap_acs(softap)
+            return channel
+        # Connect to the AP and start IPerf traffic, while we bring up softap.
+        wutils.connect_to_wifi_network(self.dut_client, network)
+        t = Thread(target=self.run_iperf_client,args=((network,self.dut_client),))
+        t.setDaemon(True)
+        t.start()
+        time.sleep(1)
+        softap = self.start_softap_and_verify(softap_band)
+        t.join()
+        channel = self.get_softap_acs(softap)
+        return channel
+
+    def verify_acs_channel(self, chan, avoid_chan):
+        """Verify ACS algorithm by ensuring that softAP came up on a channel,
+           different than the active channels.
+
+        Args:
+            chan: The channel number softap came-up on.
+            avoid_chan: The channel to avoid during this test.
+
+        """
+        if avoid_chan in range(1,12):
+            avoid_chan2 = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+        elif avoid_chan in range(36, 166):
+            avoid_chan2 = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        if chan == avoid_chan or chan == avoid_chan2:
+            raise signals.TestFailure("ACS chose the same channel that the "
+                "AP was beaconing on. Channel = %d" % chan)
+
+    """Tests"""
+    @test_tracker_info(uuid="3507bd18-e787-4380-8725-1872916d4267")
+    def test_softap_2G_clean_env(self):
+        """Test to bring up SoftAp on 2GHz in clean environment."""
+        network = None
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        if not chan in range(1, 12):
+            raise signals.TestFailure("ACS chose incorrect channel %d for 2GHz "
+                "band" % chan)
+
+    @test_tracker_info(uuid="3d18da8b-d29a-45f9-8018-5348e10099e9")
+    def test_softap_5G_clean_env(self):
+        """Test to bring up SoftAp on 5GHz in clean environment."""
+        network = None
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        if not chan in range(36, 166):
+            # Note: This does not treat DFS channel separately.
+            raise signals.TestFailure("ACS chose incorrect channel %d for 5GHz "
+                "band" % chan)
+
+    @test_tracker_info(uuid="cc353bda-3831-4d6e-b990-e501b8e4eafe")
+    def test_softap_auto_clean_env(self):
+        """Test to bring up SoftAp on AUTO-band in clean environment."""
+        network = None
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_AUTO)
+        if not chan in range(36, 166):
+            # Note: This does not treat DFS channel separately.
+            raise signals.TestFailure("ACS chose incorrect channel %d for 5GHz "
+                "band" % chan)
+
+    @test_tracker_info(uuid="a5f6a926-76d2-46a7-8136-426e35b5a5a8")
+    def test_softap_2G_avoid_channel_1(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=1)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="757e2019-b027-40bf-a562-2b01f3e5957e")
+    def test_softap_5G_avoid_channel_1(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=1)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="b96e39d1-9041-4662-a55f-22641c2e2b02")
+    def test_softap_2G_avoid_channel_2(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=2)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="941c4e2b-ae35-4b49-aa81-13d3dc44b5b6")
+    def test_softap_5G_avoid_channel_2(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=2)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="444c4a34-7f6b-4f02-9802-2e896e7d1796")
+    def test_softap_2G_avoid_channel_3(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=3)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="eccd06b1-6df5-4144-8fda-1504cb822375")
+    def test_softap_5G_avoid_channel_3(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=3)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="fb257644-2081-4c3d-8394-7a308dde0047")
+    def test_softap_2G_avoid_channel_4(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=4)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="88b9cd16-4541-408a-8607-415fe60001f2")
+    def test_softap_5G_avoid_channel_4(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=4)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="b3626ec8-50e8-412c-bdbe-5c5ade647d7b")
+    def test_softap_2G_avoid_channel_5(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=5)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="45c4396b-9b0c-44f3-adf2-ea9c86fcab1d")
+    def test_softap_5G_avoid_channel_5(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=5)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="f70634e7-c6fd-403d-8cd7-439fbbda6af0")
+    def test_softap_2G_avoid_channel_6(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=6)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="f3341136-10bc-44e2-b9a8-2d27d3284b73")
+    def test_softap_5G_avoid_channel_6(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=6)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="8129594d-1608-448b-8548-5a8c4022f2a1")
+    def test_softap_2G_avoid_channel_7(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=7)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="7b470b82-d19b-438c-8f98-ce697e0eb474")
+    def test_softap_5G_avoid_channel_7(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=7)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="11540182-d471-4bf0-8f8b-add89443c329")
+    def test_softap_2G_avoid_channel_8(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=8)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="1280067c-389e-42e9-aa75-6bfbd61340f3")
+    def test_softap_5G_avoid_channel_8(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=8)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="6feeb83c-2723-49cb-93c1-6297d4a3d853")
+    def test_softap_2G_avoid_channel_9(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=9)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="49a110cd-03e8-4e99-9327-5123eab40902")
+    def test_softap_5G_avoid_channel_9(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=9)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="a03c9e45-8763-4b5c-bead-e574fb9899a2")
+    def test_softap_2G_avoid_channel_10(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=10)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="c1a1d272-a646-4c2d-8425-09d2ae6ae8e6")
+    def test_softap_5G_avoid_channel_10(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=10)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="f38d8911-92d4-4dcd-ba23-1e1667fa1f5a")
+    def test_softap_2G_avoid_channel_11(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_2g=11)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="24cc35ba-45e3-4b7a-9bc9-25b7abe92fa9")
+    def test_softap_5G_avoid_channel_11(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_2g=11)
+        network = self.reference_networks[0]["2g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="85aef720-4f3c-43bb-9de0-615b88c2bfe0")
+    def test_softap_2G_avoid_channel_36(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=36)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="433e8db3-93b5-463e-a83c-0d4b9b9a8700")
+    def test_softap_5G_avoid_channel_36(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=36)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="326e0e42-3219-4e63-a18d-5dc32c58e7d8")
+    def test_softap_2G_avoid_channel_40(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=40)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="45953c03-c978-4775-a39b-fb7e70c8990a")
+    def test_softap_5G_avoid_channel_40(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=40)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="e8e89cec-aa27-4780-8ff8-546d5af820f7")
+    def test_softap_2G_avoid_channel_44(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=44)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="5e386d7d-d4c9-40cf-9333-06da55e11ba1")
+    def test_softap_5G_avoid_channel_44(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=44)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="cb51dfca-f8de-4dfc-b513-e590c838c766")
+    def test_softap_2G_avoid_channel_48(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=48)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="490b8ed1-196c-4941-b06b-5f0721ca440b")
+    def test_softap_5G_avoid_channel_48(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=48)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="c5ab141b-e145-4cc1-b0d7-dd610cbfb462")
+    def test_softap_2G_avoid_channel_149(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=149)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="108d7ef8-6fe7-49ba-b684-3820e881fcf0")
+    def test_softap_5G_avoid_channel_149(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=149)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="f6926f40-0afc-41c5-9b38-c95a99788ff5")
+    def test_softap_2G_avoid_channel_153(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=153)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="3d7b653b-c094-4c57-8e6a-047629b05216")
+    def test_softap_5G_avoid_channel_153(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=153)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="b866ceea-d3ca-45d4-964a-4edea96026e6")
+    def test_softap_2G_avoid_channel_157(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=157)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="03cb9163-bca3-442e-9691-6df82f8c51c7")
+    def test_softap_5G_avoid_channel_157(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=157)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="ae10f23a-da70-43c8-9991-2c2f4a602724")
+    def test_softap_2G_avoid_channel_161(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=161)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="521686c2-acfa-42d1-861b-aa10ac4dad34")
+    def test_softap_5G_avoid_channel_161(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=161)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="77ebecd7-c036-463f-b77d-2cd70d89bc81")
+    def test_softap_2G_avoid_channel_165(self):
+        """Test to configure AP and bring up SoftAp on 2G."""
+        self.configure_ap(channel_5g=165)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_2G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
+
+    @test_tracker_info(uuid="85d9386d-fe60-4708-9f91-75bbf8bec54f")
+    def test_softap_5G_avoid_channel_165(self):
+        """Test to configure AP and bring up SoftAp on 5G."""
+        self.configure_ap(channel_5g=165)
+        network = self.reference_networks[0]["5g"]
+        chan = self.start_traffic_and_softap(network, WIFI_CONFIG_APBAND_5G)
+        avoid_chan = int(sys._getframe().f_code.co_name.split('_')[-1])
+        self.verify_acs_channel(chan, avoid_chan)
diff --git a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
new file mode 100644
index 0000000..375ae93
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
@@ -0,0 +1,392 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import logging
+import os
+from acts import asserts
+from acts import base_test
+from acts.controllers import iperf_server as ipf
+from acts.controllers import iperf_client as ipc
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_sniffer
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from WifiRvrTest import WifiRvrTest
+
+AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
+                                          ['ap_settings'])
+
+
+class WifiSoftApRvrTest(WifiRvrTest):
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.tests = ('test_rvr_TCP_DL_2GHz', 'test_rvr_TCP_UL_2GHz',
+                      'test_rvr_TCP_DL_5GHz', 'test_rvr_TCP_UL_5GHz',
+                      'test_rvr_TCP_DL_2GHz_backhaul_2GHz',
+                      'test_rvr_TCP_UL_2GHz_backhaul_2GHz',
+                      'test_rvr_TCP_DL_5GHz_backhaul_2GHz',
+                      'test_rvr_TCP_UL_5GHz_backhaul_2GHz',
+                      'test_rvr_TCP_DL_2GHz_backhaul_5GHz',
+                      'test_rvr_TCP_UL_2GHz_backhaul_5GHz',
+                      'test_rvr_TCP_DL_5GHz_backhaul_5GHz',
+                      'test_rvr_TCP_UL_5GHz_backhaul_5GHz')
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardwares and compiles parameters that are
+        common to all tests in this class.
+        """
+        req_params = [
+            'sap_test_params', 'testbed_params', 'RetailAccessPoints',
+            'ap_networks'
+        ]
+        opt_params = ['golden_files_list', 'OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        self.access_points = retail_ap.create(self.RetailAccessPoints)
+        self.access_point = self.access_points[0]
+        self.testclass_params = self.sap_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.iperf_server = ipf.create([{
+            'AndroidDevice':
+            self.android_devices[0].serial,
+            'port':
+            '5201'
+        }])[0]
+        self.iperf_client = ipc.create([{
+            'AndroidDevice':
+            self.android_devices[1].serial,
+            'port':
+            '5201'
+        }])[0]
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        if not hasattr(self, 'golden_files_list'):
+            if 'golden_results_path' in self.testbed_params:
+                self.golden_files_list = [
+                    os.path.join(self.testbed_params['golden_results_path'],
+                                 file) for file in
+                    os.listdir(self.testbed_params['golden_results_path'])
+                ]
+            else:
+                self.log.warning('No golden files found.')
+                self.golden_files_list = []
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, True)
+
+    def teardown_class(self):
+        # Turn WiFi OFF
+        wutils.stop_wifi_tethering(self.android_devices[0])
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+        self.process_testclass_results()
+        # Teardown AP and release it's lockfile
+        self.access_point.teardown()
+
+    def teardown_test(self):
+        self.iperf_server.stop()
+        wutils.stop_wifi_tethering(self.android_devices[0])
+
+    def get_sap_connection_info(self):
+        info = {}
+        info['client_ip_address'] = self.android_devices[
+            1].droid.connectivityGetIPv4Addresses('wlan0')[0]
+        info['ap_ip_address'] = self.android_devices[
+            0].droid.connectivityGetIPv4Addresses('wlan1')[0]
+        info['frequency'] = self.android_devices[1].adb.shell(
+            'wpa_cli status | grep freq').split('=')[1]
+        info['channel'] = wutils.WifiEnums.freq_to_channel[int(
+            info['frequency'])]
+        info['mode'] = 'VHT20' if info['channel'] < 13 else 'VHT80'
+        return info
+
+    def setup_aps(self, testcase_params):
+        for network in testcase_params['ap_networks']:
+            self.log.info('Setting AP {} {} interface on channel {}'.format(
+                network['ap_id'], network['interface_id'], network['channel']))
+            self.access_points[network['ap_id']].set_channel(
+                network['interface_id'], network['channel'])
+
+    def setup_duts(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        self.ap_dut = self.android_devices[0]
+        self.sta_dut = self.android_devices[1]
+        for dev in self.android_devices:
+            if not wputils.health_check(dev, 20):
+                asserts.skip('DUT health check failed. Skipping test.')
+        # Reset WiFi on all devices
+        for dev in self.android_devices:
+            dev.go_to_sleep()
+            wutils.reset_wifi(dev)
+            wutils.set_wifi_country_code(dev, wutils.WifiEnums.CountryCode.US)
+
+        for network in testcase_params['ap_networks']:
+            for connected_dut in network['connected_dut']:
+                self.log.info("Connecting DUT {} to {}".format(
+                    connected_dut, self.ap_networks[network['ap_id']][
+                        network['interface_id']]))
+                wutils.wifi_connect(self.android_devices[connected_dut],
+                                    self.ap_networks[network['ap_id']][
+                                        network['interface_id']],
+                                    num_of_tries=5,
+                                    check_connectivity=True)
+
+    def setup_sap_connection(self, testcase_params):
+        # Setup Soft AP
+        sap_config = wutils.create_softap_config()
+        self.log.info('SoftAP Config: {}'.format(sap_config))
+        wutils.start_wifi_tethering(self.android_devices[0],
+                                    sap_config[wutils.WifiEnums.SSID_KEY],
+                                    sap_config[wutils.WifiEnums.PWD_KEY],
+                                    testcase_params['sap_band_enum'])
+        # Connect DUT to Network
+        testcase_params['test_network'] = {
+            'SSID': sap_config[wutils.WifiEnums.SSID_KEY],
+            'password': sap_config[wutils.WifiEnums.PWD_KEY]
+        }
+        wutils.wifi_connect(self.sta_dut,
+                            testcase_params['test_network'],
+                            num_of_tries=5,
+                            check_connectivity=False)
+        # Compile meta data
+        #self.access_point = AccessPointTuple(sap_config)
+        sap_info = self.get_sap_connection_info()
+        testcase_params['channel'] = sap_info['channel']
+        testcase_params['mode'] = sap_info['mode']
+        testcase_params['iperf_server_address'] = sap_info['ap_ip_address']
+
+    def setup_sap_rvr_test(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Configure DUTs
+        self.setup_aps(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False)
+        # Configure DUTs
+        self.setup_duts(testcase_params)
+        # Setup sap connection
+        self.setup_sap_connection(testcase_params)
+        # Set DUT to monitor RSSI and LLStats on
+        self.monitored_dut = self.sta_dut
+
+    def compile_test_params(self, testcase_params):
+        """Function that completes all test params based on the test name.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        num_atten_steps = int((self.testclass_params['atten_stop'] -
+                               self.testclass_params['atten_start']) /
+                              self.testclass_params['atten_step'])
+        testcase_params['atten_range'] = [
+            self.testclass_params['atten_start'] +
+            x * self.testclass_params['atten_step']
+            for x in range(0, num_atten_steps)
+        ]
+
+        if testcase_params['traffic_direction'] == 'DL':
+            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+                duration=self.testclass_params['iperf_duration'],
+                reverse_direction=1,
+                traffic_type=testcase_params['traffic_type'])
+            testcase_params['use_client_output'] = True
+        else:
+            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+                duration=self.testclass_params['iperf_duration'],
+                reverse_direction=0,
+                traffic_type=testcase_params['traffic_type'])
+            testcase_params['use_client_output'] = False
+
+        # Compile AP and infrastructure connection parameters
+        ap_networks = []
+        if testcase_params['dut_connected'][0]:
+            band = testcase_params['dut_connected'][0].split('_')[0]
+            ap_networks.append({
+                'ap_id': 0,
+                'interface_id': band if band == '2G' else band + '_1',
+                'band': band,
+                'channel': 1 if band == '2G' else 36,
+                'connected_dut': [0]
+            })
+
+        if testcase_params['dut_connected'][1]:
+            if testcase_params['dut_connected'][0] == testcase_params[
+                    'dut_connected'][1]:
+                # if connected to same network, add it to the above
+                ap_networks[0]['connected_dut'].append(1)
+            else:
+                band = testcase_params['dut_connected'][1].split('_')[0]
+                if not testcase_params['dut_connected'][0]:
+                    # if it's the only dut connected, assign it to ap 0
+                    ap_id = 0
+                else:
+                    ap_id = 1
+                ap_networks.append({
+                    'ap_id': ap_id,
+                    'interface_id': band if band == '2G' else band + '_1',
+                    'band': band,
+                    'channel': 11 if band == '2G' else 149,
+                    'connected_dut': [1]
+                })
+        testcase_params['ap_networks'] = ap_networks
+
+        return testcase_params
+
+    def _test_sap_rvr(self, testcase_params):
+        """ Function that gets called for each test case
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+
+        self.setup_sap_rvr_test(testcase_params)
+        result = self.run_rvr_test(testcase_params)
+        self.testclass_results.append(result)
+        self.process_test_results(result)
+        self.pass_fail_check(result)
+
+    #Test cases
+    def test_rvr_TCP_DL_2GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='2GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=[False, False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_UL_2GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='2GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=[False, False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_DL_5GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='5GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=[False, False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_UL_5GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='5GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=[False, False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_DL_2GHz_backhaul_2GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='2GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_UL_2GHz_backhaul_2GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='2GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_DL_5GHz_backhaul_2GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='5GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_UL_5GHz_backhaul_2GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='5GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_DL_2GHz_backhaul_5GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='2GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_UL_2GHz_backhaul_5GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='2GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_DL_5GHz_backhaul_5GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='5GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', False])
+        self._test_sap_rvr(testcase_params)
+
+    def test_rvr_TCP_UL_5GHz_backhaul_5GHz(self):
+        testcase_params = collections.OrderedDict(
+            sap_band='5GHz',
+            sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', False])
+        self._test_sap_rvr(testcase_params)
diff --git a/acts_tests/tests/google/wifi/WifiSoftApTest.py b/acts_tests/tests/google/wifi/WifiSoftApTest.py
new file mode 100644
index 0000000..bcaab8d
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiSoftApTest.py
@@ -0,0 +1,876 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import queue
+import random
+import time
+
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import socket_test_utils as sutils
+from acts.test_utils.tel import tel_defines
+from acts.test_utils.tel import tel_test_utils as tel_utils
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_AUTO
+from acts.test_utils.wifi import wifi_constants
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+class WifiSoftApTest(WifiBaseTest):
+
+    def setup_class(self):
+        """It will setup the required dependencies from config file and configure
+           the devices for softap mode testing.
+
+        Returns:
+            True if successfully configured the requirements for testing.
+        """
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+        req_params = ["dbs_supported_models"]
+        opt_param = ["open_network"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True)
+        self.open_network = self.open_network[0]["2g"]
+        # Do a simple version of init - mainly just sync the time and enable
+        # verbose logging.  This test will fail if the DUT has a sim and cell
+        # data is disabled.  We would also like to test with phones in less
+        # constrained states (or add variations where we specifically
+        # constrain).
+        utils.require_sl4a((self.dut, self.dut_client))
+        utils.sync_device_time(self.dut)
+        utils.sync_device_time(self.dut_client)
+        # Set country code explicitly to "US".
+        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
+        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
+        # Enable verbose logging on the duts
+        self.dut.droid.wifiEnableVerboseLogging(1)
+        asserts.assert_equal(self.dut.droid.wifiGetVerboseLoggingLevel(), 1,
+            "Failed to enable WiFi verbose logging on the softap dut.")
+        self.dut_client.droid.wifiEnableVerboseLogging(1)
+        asserts.assert_equal(self.dut_client.droid.wifiGetVerboseLoggingLevel(), 1,
+            "Failed to enable WiFi verbose logging on the client dut.")
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_toggle_state(self.dut_client, True)
+        self.AP_IFACE = 'wlan0'
+        if self.dut.model in self.dbs_supported_models:
+            self.AP_IFACE = 'wlan1'
+        if len(self.android_devices) > 2:
+            utils.sync_device_time(self.android_devices[2])
+            wutils.set_wifi_country_code(self.android_devices[2], wutils.WifiEnums.CountryCode.US)
+            self.android_devices[2].droid.wifiEnableVerboseLogging(1)
+            asserts.assert_equal(self.android_devices[2].droid.wifiGetVerboseLoggingLevel(), 1,
+                "Failed to enable WiFi verbose logging on the client dut.")
+            self.dut_client_2 = self.android_devices[2]
+
+    def teardown_class(self):
+        if self.dut.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.dut)
+        wutils.reset_wifi(self.dut)
+        wutils.reset_wifi(self.dut_client)
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            wutils.wifi_toggle_state(ad, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.log.debug("Toggling Airplane mode OFF.")
+        asserts.assert_true(utils.force_airplane_mode(self.dut, False),
+                            "Can not turn off airplane mode: %s" % self.dut.serial)
+        if self.dut.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.dut)
+        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
+
+    """ Helper Functions """
+    def create_softap_config(self):
+        """Create a softap config with ssid and password."""
+        ap_ssid = "softap_" + utils.rand_ascii_str(8)
+        ap_password = utils.rand_ascii_str(8)
+        self.dut.log.info("softap setup: %s %s", ap_ssid, ap_password)
+        config = {wutils.WifiEnums.SSID_KEY: ap_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = ap_password
+        return config
+
+    def confirm_softap_in_scan_results(self, ap_ssid):
+        """Confirm the ap started by wifi tethering is seen in scan results.
+
+        Args:
+            ap_ssid: SSID of the ap we are looking for.
+        """
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut_client, ap_ssid);
+
+    def confirm_softap_not_in_scan_results(self, ap_ssid):
+        """Confirm the ap started by wifi tethering is not seen in scan results.
+
+        Args:
+            ap_ssid: SSID of the ap we are looking for.
+        """
+        wutils.start_wifi_connection_scan_and_ensure_network_not_found(
+            self.dut_client, ap_ssid);
+
+    def check_cell_data_and_enable(self):
+        """Make sure that cell data is enabled if there is a sim present.
+
+        If a sim is active, cell data needs to be enabled to allow provisioning
+        checks through (when applicable).  This is done to relax hardware
+        requirements on DUTs - without this check, running this set of tests
+        after other wifi tests may cause failures.
+        """
+        # We do have a sim.  Make sure data is enabled so we can tether.
+        if not self.dut.droid.telephonyIsDataEnabled():
+            self.dut.log.info("need to enable data")
+            self.dut.droid.telephonyToggleDataConnection(True)
+            asserts.assert_true(self.dut.droid.telephonyIsDataEnabled(),
+                                "Failed to enable cell data for softap dut.")
+
+    def validate_full_tether_startup(self, band=None, hidden=None,
+                                     test_ping=False, test_clients=None):
+        """Test full startup of wifi tethering
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        initial_wifi_state = self.dut.droid.wifiCheckState()
+        initial_cell_state = tel_utils.is_sim_ready(self.log, self.dut)
+        self.dut.log.info("current state: %s", initial_wifi_state)
+        self.dut.log.info("is sim ready? %s", initial_cell_state)
+        if initial_cell_state:
+            self.check_cell_data_and_enable()
+        config = self.create_softap_config()
+        wutils.start_wifi_tethering(self.dut,
+                                    config[wutils.WifiEnums.SSID_KEY],
+                                    config[wutils.WifiEnums.PWD_KEY], band, hidden)
+        if hidden:
+            # First ensure it's not seen in scan results.
+            self.confirm_softap_not_in_scan_results(
+                config[wutils.WifiEnums.SSID_KEY])
+            # If the network is hidden, it should be saved on the client to be
+            # seen in scan results.
+            config[wutils.WifiEnums.HIDDEN_KEY] = True
+            ret = self.dut_client.droid.wifiAddNetwork(config)
+            asserts.assert_true(ret != -1, "Add network %r failed" % config)
+            self.dut_client.droid.wifiEnableNetwork(ret, 0)
+        self.confirm_softap_in_scan_results(config[wutils.WifiEnums.SSID_KEY])
+        if test_ping:
+            self.validate_ping_between_softap_and_client(config)
+        if test_clients:
+            if len(self.android_devices) > 2:
+                self.validate_ping_between_two_clients(config)
+        wutils.stop_wifi_tethering(self.dut)
+        asserts.assert_false(self.dut.droid.wifiIsApEnabled(),
+                             "SoftAp is still reported as running")
+        if initial_wifi_state:
+            wutils.wait_for_wifi_state(self.dut, True)
+        elif self.dut.droid.wifiCheckState():
+            asserts.fail("Wifi was disabled before softap and now it is enabled")
+
+    def validate_ping_between_softap_and_client(self, config):
+        """Test ping between softap and its client.
+
+        Connect one android device to the wifi hotspot.
+        Verify they can ping each other.
+
+        Args:
+            config: wifi network config with SSID, password
+        """
+        wutils.wifi_connect(self.dut_client, config, check_connectivity=False)
+
+        dut_ip = self.dut.droid.connectivityGetIPv4Addresses(self.AP_IFACE)[0]
+        dut_client_ip = self.dut_client.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+        self.dut.log.info("Try to ping %s" % dut_client_ip)
+        asserts.assert_true(
+            utils.adb_shell_ping(self.dut, count=10, dest_ip=dut_client_ip, timeout=20),
+            "%s ping %s failed" % (self.dut.serial, dut_client_ip))
+
+        self.dut_client.log.info("Try to ping %s" % dut_ip)
+        asserts.assert_true(
+            utils.adb_shell_ping(self.dut_client, count=10, dest_ip=dut_ip, timeout=20),
+            "%s ping %s failed" % (self.dut_client.serial, dut_ip))
+
+        wutils.stop_wifi_tethering(self.dut)
+
+    def validate_ping_between_two_clients(self, config):
+        """Test ping between softap's clients.
+
+        Connect two android device to the wifi hotspot.
+        Verify the clients can ping each other.
+
+        Args:
+            config: wifi network config with SSID, password
+        """
+        # Connect DUT to Network
+        ad1 = self.dut_client
+        ad2 = self.android_devices[2]
+
+        wutils.wifi_connect(ad1, config, check_connectivity=False)
+        wutils.wifi_connect(ad2, config, check_connectivity=False)
+        ad1_ip = ad1.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        ad2_ip = ad2.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+        # Ping each other
+        ad1.log.info("Try to ping %s" % ad2_ip)
+        asserts.assert_true(
+            utils.adb_shell_ping(ad1, count=10, dest_ip=ad2_ip, timeout=20),
+            "%s ping %s failed" % (ad1.serial, ad2_ip))
+
+        ad2.log.info("Try to ping %s" % ad1_ip)
+        asserts.assert_true(
+            utils.adb_shell_ping(ad2, count=10, dest_ip=ad1_ip, timeout=20),
+            "%s ping %s failed" % (ad2.serial, ad1_ip))
+
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="495f1252-e440-461c-87a7-2c45f369e129")
+    def test_check_wifi_tethering_supported(self):
+        """Test check for wifi tethering support.
+
+         1. Call method to check if wifi hotspot is supported
+        """
+        # TODO(silberst): wifiIsPortableHotspotSupported() is currently failing.
+        # Remove the extra check and logging when b/30800811 is resolved
+        hotspot_supported = self.dut.droid.wifiIsPortableHotspotSupported()
+        tethering_supported = self.dut.droid.connectivityIsTetheringSupported()
+        self.log.info(
+            "IsPortableHotspotSupported: %s, IsTetheringSupported %s." % (
+            hotspot_supported, tethering_supported))
+        asserts.assert_true(hotspot_supported,
+                            "DUT should support wifi tethering but is reporting false.")
+        asserts.assert_true(tethering_supported,
+                            "DUT should also support wifi tethering when called from ConnectivityManager")
+
+    @test_tracker_info(uuid="09c19c35-c708-48a5-939b-ac2bbb403d54")
+    def test_full_tether_startup(self):
+        """Test full startup of wifi tethering in default band.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup()
+
+    @test_tracker_info(uuid="6437727d-7db1-4f69-963e-f26a7797e47f")
+    def test_full_tether_startup_2G(self):
+        """Test full startup of wifi tethering in 2G band.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="970272fa-1302-429b-b261-51efb4dad779")
+    def test_full_tether_startup_5G(self):
+        """Test full startup of wifi tethering in 5G band.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="f76ed37a-519a-48b4-b260-ee3fc5a9cae0")
+    def test_full_tether_startup_auto(self):
+        """Test full startup of wifi tethering in auto-band.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_AUTO)
+
+    @test_tracker_info(uuid="d26ee4df-5dcb-4191-829f-05a10b1218a7")
+    def test_full_tether_startup_2G_hidden(self):
+        """Test full startup of wifi tethering in 2G band using hidden AP.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_2G, True)
+
+    @test_tracker_info(uuid="229cd585-a789-4c9a-8948-89fa72de9dd5")
+    def test_full_tether_startup_5G_hidden(self):
+        """Test full startup of wifi tethering in 5G band using hidden AP.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_5G, True)
+
+    @test_tracker_info(uuid="d546a143-6047-4ffd-b3c6-5ec81a38001f")
+    def test_full_tether_startup_auto_hidden(self):
+        """Test full startup of wifi tethering in auto-band using hidden AP.
+
+        1. Report current state.
+        2. Switch to AP mode.
+        3. verify SoftAP active.
+        4. Shutdown wifi tethering.
+        5. verify back to previous mode.
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_AUTO, True)
+
+    @test_tracker_info(uuid="b2f75330-bf33-4cdd-851a-de390f891ef7")
+    def test_tether_startup_while_connected_to_a_network(self):
+        """Test full startup of wifi tethering in auto-band while the device
+        is connected to a network.
+
+        1. Connect to an open network.
+        2. Turn on AP mode (in auto band).
+        3. Verify SoftAP active.
+        4. Make a client connect to the AP.
+        5. Shutdown wifi tethering.
+        6. Ensure that the client disconnected.
+        """
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_connect(self.dut, self.open_network)
+        config = self.create_softap_config()
+        wutils.start_wifi_tethering(self.dut,
+                                    config[wutils.WifiEnums.SSID_KEY],
+                                    config[wutils.WifiEnums.PWD_KEY],
+                                    WIFI_CONFIG_APBAND_AUTO)
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                             "SoftAp is not reported as running")
+        # local hotspot may not have internet connectivity
+        self.confirm_softap_in_scan_results(config[wutils.WifiEnums.SSID_KEY])
+        wutils.wifi_connect(self.dut_client, config, check_connectivity=False)
+        wutils.stop_wifi_tethering(self.dut)
+        wutils.wait_for_disconnect(self.dut_client)
+
+    @test_tracker_info(uuid="f2cf56ad-b8b9-43b6-ab15-a47b1d96b92e")
+    def test_full_tether_startup_2G_with_airplane_mode_on(self):
+        """Test full startup of wifi tethering in 2G band with
+        airplane mode on.
+
+        1. Turn on airplane mode.
+        2. Report current state.
+        3. Switch to AP mode.
+        4. verify SoftAP active.
+        5. Shutdown wifi tethering.
+        6. verify back to previous mode.
+        7. Turn off airplane mode.
+        """
+        self.dut.log.debug("Toggling Airplane mode ON.")
+        asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                            "Can not turn on airplane mode: %s" % self.dut.serial)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="883dd5b1-50c6-4958-a50f-bb4bea77ccaf")
+    def test_full_tether_startup_2G_one_client_ping_softap(self):
+        """(AP) 1 Device can connect to 2G hotspot
+
+        Steps:
+        1. Turn on DUT's 2G softap
+        2. Client connects to the softap
+        3. Client and DUT ping each other
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_2G, test_ping=True)
+
+    @test_tracker_info(uuid="6604e848-99d6-422c-9fdc-2882642438b6")
+    def test_full_tether_startup_5G_one_client_ping_softap(self):
+        """(AP) 1 Device can connect to 5G hotspot
+
+        Steps:
+        1. Turn on DUT's 5G softap
+        2. Client connects to the softap
+        3. Client and DUT ping each other
+        """
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_5G, test_ping=True)
+
+    @test_tracker_info(uuid="17725ecd-f900-4cf7-8b2d-d7515b0a595c")
+    def test_softap_2G_two_clients_ping_each_other(self):
+        """Test for 2G hotspot with 2 clients
+
+        1. Turn on 2G hotspot
+        2. Two clients connect to the hotspot
+        3. Two clients ping each other
+        """
+        asserts.skip_if(len(self.android_devices) < 3,
+                        "No extra android devices. Skip test")
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_2G, test_clients=True)
+
+    @test_tracker_info(uuid="98c09888-1021-4f79-9065-b3cf9b132146")
+    def test_softap_5G_two_clients_ping_each_other(self):
+        """Test for 5G hotspot with 2 clients
+
+        1. Turn on 5G hotspot
+        2. Two clients connect to the hotspot
+        3. Two clients ping each other
+        """
+        asserts.skip_if(len(self.android_devices) < 3,
+                        "No extra android devices. Skip test")
+        self.validate_full_tether_startup(WIFI_CONFIG_APBAND_5G, test_clients=True)
+
+    @test_tracker_info(uuid="b991129e-030a-4998-9b08-0687270bec24")
+    def test_number_of_softap_clients(self):
+        """Test for number of softap clients to be updated correctly
+
+        1. Turn of hotspot
+        2. Register softap callback
+        3. Let client connect to the hotspot
+        4. Register second softap callback
+        5. Force client connect/disconnect to hotspot
+        6. Unregister second softap callback
+        7. Force second client connect to hotspot (if supported)
+        8. Turn off hotspot
+        9. Verify second softap callback doesn't respond after unregister
+        """
+        config = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_AUTO)
+        # Register callback after softap enabled to avoid unnecessary callback
+        # impact the test
+        callbackId = self.dut.droid.registerSoftApCallback()
+        # Verify clients will update immediately after register callback
+        wutils.wait_for_expected_number_of_softap_clients(
+                self.dut, callbackId, 0)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_ENABLED_STATE)
+
+        # Force DUTs connect to Network
+        wutils.wifi_connect(self.dut_client, config,
+                check_connectivity=False)
+        wutils.wait_for_expected_number_of_softap_clients(
+                self.dut, callbackId, 1)
+
+        # Register another callback to verify multi callback clients case
+        callbackId_2 = self.dut.droid.registerSoftApCallback()
+        # Verify clients will update immediately after register callback
+        wutils.wait_for_expected_number_of_softap_clients(
+                self.dut, callbackId_2, 1)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId_2,
+                wifi_constants.WIFI_AP_ENABLED_STATE)
+
+        # Client Off/On Wifi to verify number of softap clients will be updated
+        wutils.toggle_wifi_and_wait_for_reconnection(self.dut_client, config)
+
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId_2, 0)
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 1)
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId_2, 1)
+
+        # Unregister callbackId_2 to verify multi callback clients case
+        self.dut.droid.unregisterSoftApCallback(callbackId_2)
+
+        if len(self.android_devices) > 2:
+            wutils.wifi_connect(self.android_devices[2], config,
+                    check_connectivity=False)
+            wutils.wait_for_expected_number_of_softap_clients(
+                    self.dut, callbackId, 2)
+
+        # Turn off softap when clients connected
+        wutils.stop_wifi_tethering(self.dut)
+        wutils.wait_for_disconnect(self.dut_client)
+        if len(self.android_devices) > 2:
+            wutils.wait_for_disconnect(self.android_devices[2])
+
+        # Verify client number change back to 0 after softap stop if client
+        # doesn't disconnect before softap stop
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_DISABLING_STATE)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_DISABLED_STATE)
+        wutils.wait_for_expected_number_of_softap_clients(
+                self.dut, callbackId, 0)
+        # Unregister callback
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+        # Check no any callbackId_2 event after unregister
+        asserts.assert_equal(
+                wutils.get_current_number_of_softap_clients(
+                self.dut, callbackId_2), None)
+
+    @test_tracker_info(uuid="35bc4ba1-bade-42ee-a563-0c73afb2402a")
+    def test_softap_auto_shut_off(self):
+        """Test for softap auto shut off
+
+        1. Turn off hotspot
+        2. Register softap callback
+        3. Let client connect to the hotspot
+        4. Start wait [wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S] seconds
+        5. Check hotspot doesn't shut off
+        6. Let client disconnect to the hotspot
+        7. Start wait [wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S] seconds
+        8. Check hotspot auto shut off
+        """
+        config = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_AUTO)
+        # Register callback after softap enabled to avoid unnecessary callback
+        # impact the test
+        callbackId = self.dut.droid.registerSoftApCallback()
+        # Verify clients will update immediately after register callback
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_ENABLED_STATE)
+
+        # Force DUTs connect to Network
+        wutils.wifi_connect(self.dut_client, config, check_connectivity=False)
+        wutils.wait_for_expected_number_of_softap_clients(
+                self.dut, callbackId, 1)
+
+        self.dut.log.info("Start waiting %s seconds with 1 clients ",
+                wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+        time.sleep(wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+
+        # When client connected, softap should keep as enabled
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                "SoftAp is not reported as running")
+
+        wutils.wifi_toggle_state(self.dut_client, False)
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        self.dut.log.info("Start waiting %s seconds with 0 client",
+                wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+        time.sleep(wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+        # Softap should stop since no client connected
+        # doesn't disconnect before softap stop
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_DISABLING_STATE)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_DISABLED_STATE)
+        asserts.assert_false(self.dut.droid.wifiIsApEnabled(),
+                "SoftAp is not reported as running")
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+    @test_tracker_info(uuid="3a10c7fd-cd8d-4d46-9d12-88a68640e060")
+    def test_softap_auto_shut_off_with_customized_timeout(self):
+        """Test for softap auto shut off
+        1. Turn on hotspot
+        2. Register softap callback
+        3. Backup original shutdown timeout value
+        4. Set up test_shutdown_timeout_value
+        5. Let client connect to the hotspot
+        6. Start wait test_shutdown_timeout_value * 1.1 seconds
+        7. Check hotspot doesn't shut off
+        8. Let client disconnect to the hotspot
+        9. Start wait test_shutdown_timeout_value seconds
+        10. Check hotspot auto shut off
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+        # This config only included SSID and Password which used for connection
+        # only.
+        config = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_AUTO)
+
+        # Get current configuration to use for update configuration
+        current_softap_config = self.dut.droid.wifiGetApConfiguration()
+        # Register callback after softap enabled to avoid unnecessary callback
+        # impact the test
+        callbackId = self.dut.droid.registerSoftApCallback()
+        # Verify clients will update immediately after register callback
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_ENABLED_STATE)
+
+        # Setup shutdown timeout value
+        test_shutdown_timeout_value_s = 10
+        wutils.save_wifi_soft_ap_config(self.dut, current_softap_config,
+            shutdown_timeout_millis=test_shutdown_timeout_value_s * 1000)
+        # Force DUTs connect to Network
+        wutils.wifi_connect(self.dut_client, config, check_connectivity=False)
+        wutils.wait_for_expected_number_of_softap_clients(
+                self.dut, callbackId, 1)
+
+        self.dut.log.info("Start waiting %s seconds with 1 clients ",
+                test_shutdown_timeout_value_s * 1.1)
+        time.sleep(test_shutdown_timeout_value_s * 1.1)
+
+        # When client connected, softap should keep as enabled
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                "SoftAp is not reported as running")
+
+        wutils.wifi_toggle_state(self.dut_client, False)
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        self.dut.log.info("Start waiting %s seconds with 0 client",
+                test_shutdown_timeout_value_s * 1.1)
+        time.sleep(test_shutdown_timeout_value_s * 1.1)
+        # Softap should stop since no client connected
+        # doesn't disconnect before softap stop
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_DISABLING_STATE)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_DISABLED_STATE)
+        asserts.assert_false(self.dut.droid.wifiIsApEnabled(),
+                "SoftAp is not reported as running")
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="a9444699-f0d3-4ac3-922b-05e9d4f67968")
+    def test_softap_configuration_update(self):
+      """Test for softap configuration update
+        1. Get current softap configuration
+        2. Update to Open Security configuration
+        3. Update to WPA2_PSK configuration
+        4. Restore the configuration
+      """
+      # Backup config
+      original_softap_config = self.dut.droid.wifiGetApConfiguration()
+      wutils.save_wifi_soft_ap_config(self.dut, {"SSID":"ACTS_TEST"},
+          band=WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G, hidden=False,
+          security=WifiEnums.SoftApSecurityType.OPEN, password="",
+          channel=11, max_clients=0, shutdown_timeout_enable=False,
+          shutdown_timeout_millis=0, client_control_enable=True,
+          allowedList=[], blockedList=[])
+
+      wutils.save_wifi_soft_ap_config(self.dut, {"SSID":"ACTS_TEST"},
+          band=WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G, hidden=True,
+          security=WifiEnums.SoftApSecurityType.WPA2, password="12345678",
+          channel=0, max_clients=1, shutdown_timeout_enable=True,
+          shutdown_timeout_millis=10000, client_control_enable=False,
+          allowedList=["aa:bb:cc:dd:ee:ff"], blockedList=["11:22:33:44:55:66"])
+
+      # Restore config
+      wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="8a5d81fa-649c-4679-a823-5cef50828a94")
+    def test_softap_client_control(self):
+        """Test Client Control feature
+        1. Check SoftApCapability to make sure feature is supported
+        2. Backup config
+        3. Setup configuration which used to start softap
+        4. Register callback after softap enabled
+        5. Trigger client connect to softap
+        6. Verify blocking event
+        7. Add client into allowed list
+        8. Verify client connected
+        9. Restore Config
+        """
+        # Register callback to check capability first
+        callbackId = self.dut.droid.registerSoftApCallback()
+        # Check capability first to make sure DUT support this test.
+        capabilityEventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_CAPABILITY_CHANGED
+        capability = self.dut.ed.pop_event(capabilityEventStr, 10)
+        asserts.skip_if(not capability['data'][wifi_constants
+            .SOFTAP_CAPABILITY_FEATURE_CLIENT_CONTROL],
+            "Client control isn't supported, ignore test")
+
+        # Unregister callback before start test to avoid
+        # unnecessary callback impact the test
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+        # start the test
+
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+        # Setup configuration which used to start softap
+        wutils.save_wifi_soft_ap_config(self.dut, {"SSID":"ACTS_TEST"},
+            band=WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G, hidden=False,
+            security=WifiEnums.SoftApSecurityType.WPA2, password="12345678",
+            client_control_enable=True)
+
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        current_softap_config = self.dut.droid.wifiGetApConfiguration()
+        # Register callback after softap enabled to avoid unnecessary callback
+        # impact the test
+        callbackId = self.dut.droid.registerSoftApCallback()
+
+        # Verify clients will update immediately after register callback
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_ENABLED_STATE)
+
+        # Trigger client connection
+        self.dut_client.droid.wifiConnectByConfig(current_softap_config)
+
+        eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_BLOCKING_CLIENT_CONNECTING
+        blockedClient = self.dut.ed.pop_event(eventStr, 10)
+        asserts.assert_equal(blockedClient['data'][wifi_constants.
+            SOFTAP_BLOCKING_CLIENT_REASON_KEY],
+            wifi_constants.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+            "Blocked reason code doesn't match")
+
+        # Update configuration, add client into allowed list
+        wutils.save_wifi_soft_ap_config(self.dut, current_softap_config,
+            allowedList=[blockedClient['data'][wifi_constants.
+            SOFTAP_BLOCKING_CLIENT_WIFICLIENT_KEY]])
+
+        # Wait configuration updated
+        time.sleep(3)
+        # Trigger connection again
+        self.dut_client.droid.wifiConnectByConfig(current_softap_config)
+
+        # Verify client connected
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 1)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+        # Unregister callback
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+    @test_tracker_info(uuid="d0b61b58-fa2b-4ced-bc52-3366cb826e79")
+    def test_softap_max_client_setting(self):
+        """Test Client Control feature
+        1. Check device number and capability to make sure feature is supported
+        2. Backup config
+        3. Setup configuration which used to start softap
+        4. Register callback after softap enabled
+        5. Trigger client connect to softap
+        6. Verify blocking event
+        7. Extend max client setting
+        8. Verify client connected
+        9. Restore Config
+        """
+        asserts.skip_if(len(self.android_devices) < 3,
+                        "Device less than 3, skip the test.")
+        # Register callback to check capability first
+        callbackId = self.dut.droid.registerSoftApCallback()
+        # Check capability first to make sure DUT support this test.
+        capabilityEventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_CAPABILITY_CHANGED
+        capability = self.dut.ed.pop_event(capabilityEventStr, 10)
+        asserts.skip_if(not capability['data'][wifi_constants
+            .SOFTAP_CAPABILITY_FEATURE_CLIENT_CONTROL],
+            "Client control isn't supported, ignore test")
+
+        # Unregister callback before start test to avoid
+        # unnecessary callback impact the test
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+        # Setup configuration which used to start softap
+        wutils.save_wifi_soft_ap_config(self.dut, {"SSID":"ACTS_TEST"},
+            band=WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G, hidden=False,
+            security=WifiEnums.SoftApSecurityType.WPA2, password="12345678",
+            max_clients=1)
+
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        current_softap_config = self.dut.droid.wifiGetApConfiguration()
+        # Register callback again after softap enabled to avoid
+        # unnecessary callback impact the test
+        callbackId = self.dut.droid.registerSoftApCallback()
+
+        # Verify clients will update immediately after register calliback
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 0)
+        wutils.wait_for_expected_softap_state(self.dut, callbackId,
+                wifi_constants.WIFI_AP_ENABLED_STATE)
+
+        # Trigger client connection
+        self.dut_client.droid.wifiConnectByConfig(current_softap_config)
+        self.dut_client_2.droid.wifiConnectByConfig(current_softap_config)
+        # Wait client connect
+        time.sleep(3)
+
+        # Verify one client connected and one blocked due to max client
+        eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_BLOCKING_CLIENT_CONNECTING
+        blockedClient = self.dut.ed.pop_event(eventStr, 10)
+        asserts.assert_equal(blockedClient['data'][wifi_constants.
+            SOFTAP_BLOCKING_CLIENT_REASON_KEY],
+            wifi_constants.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+            "Blocked reason code doesn't match")
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 1)
+
+        # Update configuration, extend client to 2
+        wutils.save_wifi_soft_ap_config(self.dut, current_softap_config,
+            max_clients=2)
+
+        # Wait configuration updated
+        time.sleep(3)
+        # Trigger connection again
+        self.dut_client_2.droid.wifiConnectByConfig(current_softap_config)
+        # Wait client connect
+        time.sleep(3)
+        # Verify client connected
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                callbackId, 2)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+        # Unregister callback
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+    @test_tracker_info(uuid="07b4e5b3-48ce-49b9-a83e-3e288bb88e91")
+    def test_softap_5g_preferred_country_code_de(self):
+        """Verify softap works when set to 5G preferred band
+           with country code 'DE'.
+
+        Steps:
+            1. Set country code to Germany
+            2. Save a softap configuration set to 5G preferred band.
+            3. Start softap and verify it works
+            4. Verify a client device can connect to it.
+        """
+        wutils.set_wifi_country_code(
+            self.dut, wutils.WifiEnums.CountryCode.GERMANY)
+        sap_config = self.create_softap_config()
+        wifi_network = sap_config.copy()
+        sap_config[
+            WifiEnums.AP_BAND_KEY] = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G
+        sap_config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA2
+        asserts.assert_true(
+            self.dut.droid.wifiSetWifiApConfiguration(sap_config),
+            "Failed to set WifiAp Configuration")
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        softap_conf = self.dut.droid.wifiGetApConfiguration()
+        self.log.info("softap conf: %s" % softap_conf)
+        sap_band = softap_conf[WifiEnums.AP_BAND_KEY]
+        asserts.assert_true(
+            sap_band == WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G,
+            "Soft AP didn't start in 5G preferred band")
+        wutils.connect_to_wifi_network(self.dut_client, wifi_network)
+    """ Tests End """
+
+
+if __name__ == "__main__":
+    pass
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
new file mode 100755
index 0000000..f84c06c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import pprint
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+WifiEnums = wutils.WifiEnums
+
+# Channels to configure the AP for various test scenarios.
+WIFI_NETWORK_AP_CHANNEL_2G = 1
+WIFI_NETWORK_AP_CHANNEL_5G = 36
+WIFI_NETWORK_AP_CHANNEL_5G_DFS = 132
+
+class WifiStaApConcurrencyStressTest(WifiStaApConcurrencyTest):
+    """Stress tests for STA + AP concurrency scenarios.
+
+    Test Bed Requirement:
+    * At least two Android devices (For AP)
+    * One Wi-Fi network visible to the device (for STA).
+    """
+
+    def __init__(self, controllers):
+        WifiStaApConcurrencyTest.__init__(self, controllers)
+        self.tests = ("test_stress_wifi_connection_2G_softap_2G",
+                      "test_stress_wifi_connection_5G_softap_5G",
+                      "test_stress_wifi_connection_5G_DFS_softap_5G",
+                      "test_stress_wifi_connection_5G_softap_2G",
+                      "test_stress_wifi_connection_5G_DFS_softap_2G",
+                      "test_stress_wifi_connection_2G_softap_5G",
+                      "test_stress_wifi_connection_5G_softap_2G_with_location_scan_on",
+                      "test_stress_softap_2G_wifi_connection_2G",
+                      "test_stress_softap_5G_wifi_connection_5G",
+                      "test_stress_softap_5G_wifi_connection_5G_DFS",
+                      "test_stress_softap_5G_wifi_connection_2G",
+                      "test_stress_softap_2G_wifi_connection_5G",
+                      "test_stress_softap_2G_wifi_connection_5G_DFS",
+                      "test_stress_softap_5G_wifi_connection_2G_with_location_scan_on")
+
+    def setup_class(self):
+        super().setup_class()
+        opt_param = ["stress_count"]
+        self.unpack_userparams(opt_param_names=opt_param)
+
+    """Helper Functions"""
+    def connect_to_wifi_network_and_verify(self, params):
+        """Connection logic for open and psk wifi networks.
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+        """
+        network, ad = params
+        SSID = network[WifiEnums.SSID_KEY]
+        wutils.reset_wifi(ad)
+        wutils.connect_to_wifi_network(ad, network)
+        if len(self.android_devices) > 2:
+            wutils.reset_wifi(self.android_devices[2])
+            wutils.connect_to_wifi_network(self.android_devices[2], network)
+
+    def verify_wifi_full_on_off(self, network, softap_config):
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((network, self.dut))
+        if len(self.android_devices) > 2:
+            self.log.info("Testbed has extra android devices, do more validation")
+            self.verify_traffic_between_dut_clients(
+                    self.dut, self.android_devices[2])
+        wutils.wifi_toggle_state(self.dut, False)
+
+    def verify_softap_full_on_off(self, network, softap_band):
+        softap_config = self.start_softap_and_verify(softap_band)
+        if len(self.android_devices) > 2:
+            self.log.info("Testbed has extra android devices, do more validation")
+            self.verify_traffic_between_dut_clients(
+                    self.dut_client, self.android_devices[2])
+        wutils.reset_wifi(self.dut_client)
+        if len(self.android_devices) > 2:
+            wutils.reset_wifi(self.android_devices[2])
+        wutils.stop_wifi_tethering(self.dut)
+
+    """Tests"""
+    @test_tracker_info(uuid="615997cc-8290-4af3-b3ac-1f5bd5af6ed1")
+    def test_stress_wifi_connection_2G_softap_2G(self):
+        """Tests connection to 2G network the enable/disable SoftAp on 2G N times.
+        """
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_2g, self.dut))
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_softap_full_on_off(self.open_2g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="03362d54-a624-4fb8-ad97-7abb9e6f655c")
+    def test_stress_wifi_connection_5G_softap_5G(self):
+        """Tests connection to 5G network followed by bringing up SoftAp on 5G.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_5g, self.dut))
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_softap_full_on_off(self.open_5g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="fdda4ff2-38d5-4398-9a59-c7cee407a2b3")
+    def test_stress_wifi_connection_5G_DFS_softap_5G(self):
+        """Tests connection to 5G DFS network followed by bringing up SoftAp on 5G.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_5g, self.dut))
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_softap_full_on_off(self.open_5g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="b3621721-7714-43eb-8438-b578164b9194")
+    def test_stress_wifi_connection_5G_softap_2G(self):
+        """Tests connection to 5G network followed by bringing up SoftAp on 2G.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_5g, self.dut))
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_softap_full_on_off(self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="bde1443f-f912-408e-b01a-537548dd023c")
+    def test_stress_wifi_connection_5G_DFS_softap_2G(self):
+        """Tests connection to 5G DFS network followed by bringing up SoftAp on 2G.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_5g, self.dut))
+        for count in range(self.stress_count):
+            self.verify_softap_full_on_off(self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="2b6a891a-e0d6-4660-abf6-579099ce6924")
+    def test_stress_wifi_connection_2G_softap_5G(self):
+        """Tests connection to 2G network followed by bringing up SoftAp on 5G.
+        """
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_2g, self.dut))
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_softap_full_on_off(self.open_2g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="f28abf22-9df0-4500-b342-6682ca305e60")
+    def test_stress_wifi_connection_5G_softap_2G_with_location_scan_on(self):
+        """Tests connection to 5G network followed by bringing up SoftAp on 2G
+        with location scans turned on.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.turn_location_on_and_scan_toggle_on()
+        wutils.wifi_toggle_state(self.dut, True)
+        self.connect_to_wifi_network_and_verify((self.open_5g, self.dut))
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_softap_full_on_off(self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="0edb1500-6c60-442e-9268-a2ad9ee2b55c")
+    def test_stress_softap_2G_wifi_connection_2G(self):
+        """Tests enable SoftAp on 2G then connection/disconnection to 2G network for N times.
+        """
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_2G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_2g, softap_config)
+
+    @test_tracker_info(uuid="162a6679-edd5-4daa-9f25-75d79cf4bb4a")
+    def test_stress_softap_5G_wifi_connection_5G(self):
+        """Tests enable SoftAp on 5G then connection/disconnection to 5G network for N times.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_5G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_5g, softap_config)
+
+    @test_tracker_info(uuid="ee98f2dd-c4f9-4f48-ab59-f577267760d5")
+    def test_stress_softap_5G_wifi_connection_5G_DFS(self):
+        """Tests enable SoftAp on 5G then connection/disconnection to 5G DFS network for N times.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_5G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_5g, softap_config)
+
+    @test_tracker_info(uuid="b50750b5-d5b9-4687-b9e7-9fb15f54b428")
+    def test_stress_softap_5G_wifi_connection_2G(self):
+        """Tests enable SoftAp on 5G then connection/disconnection to 2G network for N times.
+        """
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_5G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_2g, softap_config)
+
+    @test_tracker_info(uuid="9a2865db-8e4b-4339-9999-000ce9b6970b")
+    def test_stress_softap_2G_wifi_connection_5G(self):
+        """Tests enable SoftAp on 2G then connection/disconnection to 5G network for N times.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_2G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_5g, softap_config)
+
+    @test_tracker_info(uuid="add6609d-91d6-4b89-94c5-0ad8b941e3d1")
+    def test_stress_softap_2G_wifi_connection_5G_DFS(self):
+        """Tests enable SoftAp on 2G then connection/disconnection to 5G DFS network for N times.
+        """
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_2G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_5g, softap_config)
+
+    @test_tracker_info(uuid="ee42afb6-99d0-4330-933f-d4dd8c3626c6")
+    def test_stress_softap_5G_wifi_connection_2G_with_location_scan_on(self):
+        """Tests enable SoftAp on 5G then connection/disconnection to 2G network for N times
+        with location scans turned on.
+        """
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.turn_location_on_and_scan_toggle_on()
+        softap_config = self.start_softap_and_verify(
+                WIFI_CONFIG_APBAND_5G, check_connectivity=False)
+        for count in range(self.stress_count):
+            self.log.info("Iteration %d", count+1)
+            self.verify_wifi_full_on_off(self.open_2g, softap_config)
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
new file mode 100644
index 0000000..035cac3
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
@@ -0,0 +1,434 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import pprint
+import time
+import re
+
+from acts import asserts
+from acts import base_test
+from acts.controllers.ap_lib import hostapd_constants
+import acts.signals as signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts.utils as utils
+import acts.test_utils.tel.tel_test_utils as tel_utils
+
+
+WifiEnums = wutils.WifiEnums
+WLAN = "wlan0"
+# Channels to configure the AP for various test scenarios.
+WIFI_NETWORK_AP_CHANNEL_2G = 1
+WIFI_NETWORK_AP_CHANNEL_5G = 36
+WIFI_NETWORK_AP_CHANNEL_5G_DFS = 132
+
+
+class WifiStaApConcurrencyTest(WifiBaseTest):
+    """Tests for STA + AP concurrency scenarios.
+
+    Test Bed Requirement:
+    * Two Android devices (For AP)
+    * One Wi-Fi network visible to the device (for STA).
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
+
+        # Do a simple version of init - mainly just sync the time and enable
+        # verbose logging.  This test will fail if the DUT has a sim and cell
+        # data is disabled.  We would also like to test with phones in less
+        # constrained states (or add variations where we specifically
+        # constrain).
+        utils.require_sl4a(self.android_devices)
+
+        for ad in self.android_devices:
+            wutils.wifi_test_device_init(ad)
+            utils.sync_device_time(ad)
+            # Set country code explicitly to "US".
+            wutils.set_wifi_country_code(ad, WifiEnums.CountryCode.US)
+            # Enable verbose logging on the duts.
+            ad.droid.wifiEnableVerboseLogging(1)
+
+        req_params = ["dbs_supported_models",
+                      "iperf_server_address",
+                      "iperf_server_port"]
+        self.unpack_userparams(req_param_names=req_params,)
+        asserts.abort_class_if(
+            self.dut.model not in self.dbs_supported_models,
+            "Device %s does not support dual interfaces." % self.dut.model)
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+        self.turn_location_off_and_scan_toggle_off()
+
+    def teardown_test(self):
+        super().teardown_test()
+        # Prevent the stop wifi tethering failure to block ap close
+        try:
+            wutils.stop_wifi_tethering(self.dut)
+        except signals.TestFailure:
+            pass
+        for ad in self.android_devices:
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+            wutils.reset_wifi(ad)
+        self.turn_location_on_and_scan_toggle_on()
+        wutils.wifi_toggle_state(self.dut, True)
+        self.access_points[0].close()
+        if "AccessPoint" in self.user_params:
+            try:
+                del self.user_params["reference_networks"]
+                del self.user_params["open_network"]
+            except KeyError as e:
+                self.log.warn("There is no 'reference_network' or "
+                              "'open_network' to delete")
+
+    ### Helper Functions ###
+
+    def configure_ap(self, channel_2g=None, channel_5g=None):
+        """Configure and bring up AP on required channel.
+
+        Args:
+            channel_2g: The channel number to use for 2GHz network.
+            channel_5g: The channel number to use for 5GHz network.
+
+        """
+        if not channel_2g:
+            channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        if not channel_5g:
+            channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(channel_2g=channel_2g,
+                                               channel_5g=channel_5g)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                channel_2g=channel_2g,
+                                                channel_5g=channel_5g)
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
+
+    def turn_location_on_and_scan_toggle_on(self):
+        """Turns on wifi location scans."""
+        utils.set_location_service(self.dut, True)
+        self.dut.droid.wifiScannerToggleAlwaysAvailable(True)
+        msg = "Failed to turn on location service's scan."
+        asserts.assert_true(self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
+
+    def turn_location_off_and_scan_toggle_off(self):
+        """Turns off wifi location scans."""
+        utils.set_location_service(self.dut, False)
+        self.dut.droid.wifiScannerToggleAlwaysAvailable(False)
+        msg = "Failed to turn off location service's scan."
+        asserts.assert_false(self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
+
+    def run_iperf_client(self, params):
+        """Run iperf traffic after connection.
+
+        Args:
+            params: A tuple of network info and AndroidDevice object.
+        """
+        if "iperf_server_address" in self.user_params:
+            wait_time = 5
+            network, ad = params
+            ssid = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic through {}".format(ssid))
+            time.sleep(wait_time)
+            port_arg = "-p {}".format(self.iperf_server_port)
+            success, data = ad.run_iperf_client(self.iperf_server_address,
+                                                port_arg)
+            self.log.debug(pprint.pformat(data))
+            asserts.assert_true(success, "Error occurred in iPerf traffic.")
+
+    def create_softap_config(self):
+        """Create a softap config with ssid and password."""
+        ap_ssid = "softap_" + utils.rand_ascii_str(8)
+        ap_password = utils.rand_ascii_str(8)
+        self.dut.log.info("softap setup: %s %s", ap_ssid, ap_password)
+        config = {wutils.WifiEnums.SSID_KEY: ap_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = ap_password
+        return config
+
+    def start_softap_and_verify(self, band, check_connectivity=True):
+        """Test startup of softap.
+
+        1. Bring up AP mode.
+        2. Verify SoftAP active using the client device.
+
+        Args:
+            band: wifi band to start soft ap on
+            check_connectivity: If set, verify internet connectivity
+
+        Returns:
+            Softap config
+        """
+        config = self.create_softap_config()
+        wutils.start_wifi_tethering(self.dut,
+                                    config[WifiEnums.SSID_KEY],
+                                    config[WifiEnums.PWD_KEY],
+                                    band)
+        for ad in self.android_devices[1:]:
+            wutils.connect_to_wifi_network(
+                ad, config, check_connectivity=check_connectivity)
+        return config
+
+    def connect_to_wifi_network_and_start_softap(self, nw_params, softap_band):
+        """Test concurrent wifi connection and softap.
+
+        This helper method first makes a wifi connection and then starts SoftAp.
+        1. Bring up wifi.
+        2. Establish connection to a network.
+        3. Bring up softap and verify AP can be connected by a client device.
+        4. Run iperf on the wifi/softap connection to the network.
+
+        Args:
+            nw_params: Params for network STA connection.
+            softap_band: Band for the AP.
+        """
+        wutils.connect_to_wifi_network(self.dut, nw_params)
+        softap_config = self.start_softap_and_verify(softap_band)
+        self.run_iperf_client((nw_params, self.dut))
+        self.run_iperf_client((softap_config, self.dut_client))
+
+        if len(self.android_devices) > 2:
+            self.log.info("Testbed has extra devices, do more validation")
+            self.verify_traffic_between_dut_clients(
+                self.dut_client, self.android_devices[2])
+
+        asserts.assert_true(self.dut.droid.wifiCheckState(),
+                            "Wifi is not reported as running")
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                            "SoftAp is not reported as running")
+
+    def start_softap_and_connect_to_wifi_network(self, nw_params, softap_band):
+        """Test concurrent wifi connection and softap.
+
+        This helper method first starts SoftAp and then makes a wifi connection.
+        1. Bring up softap and verify AP can be connected by a client device.
+        2. Bring up wifi.
+        3. Establish connection to a network.
+        4. Run iperf on the wifi/softap connection to the network.
+        5. Verify wifi state and softap state.
+
+        Args:
+            nw_params: Params for network STA connection.
+            softap_band: Band for the AP.
+        """
+        softap_config = self.start_softap_and_verify(softap_band, False)
+        wutils.connect_to_wifi_network(self.dut, nw_params)
+        self.run_iperf_client((nw_params, self.dut))
+        self.run_iperf_client((softap_config, self.dut_client))
+
+        if len(self.android_devices) > 2:
+            self.log.info("Testbed has extra devices, do more validation")
+            self.verify_traffic_between_dut_clients(
+                self.dut, self.android_devices[2])
+
+        asserts.assert_true(self.dut.droid.wifiCheckState(),
+                            "Wifi is not reported as running")
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                            "SoftAp is not reported as running")
+
+    def verify_traffic_between_dut_clients(self, ad1, ad2, num_of_tries=2):
+        """Test the clients that connect to DUT's softap can ping each other.
+
+        Args:
+            ad1: DUT 1
+            ad2: DUT 2
+            num_of_tries: the retry times of ping test.
+        """
+        ad1_ip = ad1.droid.connectivityGetIPv4Addresses(WLAN)[0]
+        ad2_ip = ad2.droid.connectivityGetIPv4Addresses(WLAN)[0]
+        # Ping each other
+        for _ in range(num_of_tries):
+            if utils.adb_shell_ping(ad1, count=10, dest_ip=ad2_ip, timeout=20):
+                break
+        else:
+            asserts.fail("%s ping %s failed" % (ad1.serial, ad2_ip))
+        for _ in range(num_of_tries):
+            if utils.adb_shell_ping(ad2, count=10, dest_ip=ad1_ip, timeout=20):
+                break
+        else:
+            asserts.fail("%s ping %s failed" % (ad2.serial, ad1_ip))
+
+    def softap_change_band(self, ad):
+        """
+        Switch DUT SoftAp to 5G band if currently in 2G.
+        Switch DUT SoftAp to 2G band if currently in 5G.
+        """
+        wlan1_freq = int(self.get_wlan1_status(self.dut)['freq'])
+        if wlan1_freq in wutils.WifiEnums.ALL_5G_FREQUENCIES:
+            band = WIFI_CONFIG_APBAND_2G
+        elif wlan1_freq in wutils.WifiEnums.ALL_2G_FREQUENCIES:
+            band = WIFI_CONFIG_APBAND_5G
+        wutils.stop_wifi_tethering(ad)
+        self.start_softap_and_verify(band)
+
+    def get_wlan1_status(self, ad):
+        """ get wlan1 interface status"""
+        get_wlan1 = 'hostapd_cli status'
+        out_wlan1 = ad.adb.shell(get_wlan1)
+        out_wlan1 = dict(re.findall(r'(\S+)=(".*?"|\S+)', out_wlan1))
+        return out_wlan1
+
+    def enable_mobile_data(self, ad):
+        """Make sure that cell data is enabled if there is a sim present."""
+        init_sim_state = tel_utils.is_sim_ready(self.log, ad)
+        if init_sim_state:
+            if not ad.droid.telephonyIsDataEnabled():
+                ad.droid.telephonyToggleDataConnection(True)
+            asserts.assert_true(ad.droid.telephonyIsDataEnabled(),
+                                "Failed to enable Mobile Data")
+        else:
+            raise signals.TestSkip("Please insert sim card with "
+                                   "Mobile Data enabled before test")
+
+    ### Tests ###
+
+    @test_tracker_info(uuid="c396e7ac-cf22-4736-a623-aa6d3c50193a")
+    def test_wifi_connection_2G_softap_2G(self):
+        """Test connection to 2G network followed by SoftAp on 2G."""
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_2g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="1cd6120d-3db4-4624-9bae-55c976533a48")
+    def test_wifi_connection_5G_softap_5G(self):
+        """Test connection to 5G network followed by SoftAp on 5G."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="5f980007-3490-413e-b94e-7700ffab8534")
+    def test_wifi_connection_5G_DFS_softap_5G(self):
+        """Test connection to 5G DFS network followed by SoftAp on 5G."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="d05d5d44-c738-4372-9f01-ce2a640a2f25")
+    def test_wifi_connection_5G_softap_2G(self):
+        """Test connection to 5G network followed by SoftAp on 2G."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="909ac713-1ad3-4dad-9be3-ad60f00ed25e")
+    def test_wifi_connection_5G_DFS_softap_2G(self):
+        """Test connection to 5G DFS network followed by SoftAp on 2G."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="e8de724a-25d3-4801-94cc-22e9e0ecc8d1")
+    def test_wifi_connection_2G_softap_5G(self):
+        """Test connection to 2G network followed by SoftAp on 5G."""
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_2g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="647f4e17-5c7a-4249-98af-f791d163a39f")
+    def test_wifi_connection_5G_softap_2G_with_location_scan_on(self):
+        """Test connection to 5G network, SoftAp on 2G with location scan on."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.turn_location_on_and_scan_toggle_on()
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
+        # Now toggle wifi off & ensure we can still scan.
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, self.open_5g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="4aa56c11-e5bc-480b-bd61-4b4ee577a5da")
+    def test_softap_2G_wifi_connection_2G(self):
+        """Test SoftAp on 2G followed by connection to 2G network."""
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_2g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="5f954957-ad20-4de1-b20c-6c97d0463bdd")
+    def test_softap_5G_wifi_connection_5G(self):
+        """Test SoftAp on 5G followed by connection to 5G network."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="1306aafc-a07e-4654-ba78-674f90cf748e")
+    def test_softap_5G_wifi_connection_5G_DFS(self):
+        """Test SoftAp on 5G followed by connection to 5G DFS network."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="5e28e8b5-3faa-4cff-a782-13a796d7f572")
+    def test_softap_5G_wifi_connection_2G(self):
+        """Test SoftAp on 5G followed by connection to 2G network."""
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_2g, WIFI_CONFIG_APBAND_5G)
+
+    @test_tracker_info(uuid="a2c62bc6-9ccd-4bc4-8a23-9a1b5d0b4b5c")
+    def test_softap_2G_wifi_connection_5G(self):
+        """Test SoftAp on 2G followed by connection to 5G network."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="75400685-a9d9-4091-8af3-97bd539c246a")
+    def test_softap_2G_wifi_connection_5G_DFS(self):
+        """Test SoftAp on 2G followed by connection to 5G DFS network."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
+
+    @test_tracker_info(uuid="aa23a3fc-31a1-4d5c-8cf5-2eb9fdf9e7ce")
+    def test_softap_5G_wifi_connection_2G_with_location_scan_on(self):
+        """Test SoftAp on 5G, connection to 2G network with location scan on."""
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.turn_location_on_and_scan_toggle_on()
+        self.start_softap_and_connect_to_wifi_network(
+            self.open_2g, WIFI_CONFIG_APBAND_5G)
+        # Now toggle wifi off & ensure we can still scan.
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, self.open_2g[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="9decb951-4500-4476-8161-f4054760f709")
+    def test_wifi_connection_2G_softap_2G_to_softap_5g(self):
+        """Test connection to 2G network followed by SoftAp on 2G,
+        and switch SoftAp to 5G."""
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_2g, WIFI_CONFIG_APBAND_2G)
+        self.softap_change_band(self.dut)
+
+    @test_tracker_info(uuid="e17e0fb8-2c1d-4f3c-af2a-7374485f210c")
+    def test_wifi_connection_5G_softap_2G_to_softap_5g(self):
+        """Test connection to 5G network followed by SoftAp on 2G,
+        and switch SoftAp to 5G."""
+        self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_2g, WIFI_CONFIG_APBAND_2G)
+        self.softap_change_band(self.dut)
+
diff --git a/acts_tests/tests/google/wifi/WifiStressTest.py b/acts_tests/tests/google/wifi/WifiStressTest.py
new file mode 100644
index 0000000..10ba578
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiStressTest.py
@@ -0,0 +1,533 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import pprint
+import queue
+import threading
+import time
+
+import acts.base_test
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.test_utils.tel.tel_test_utils as tutils
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+WifiEnums = wutils.WifiEnums
+
+WAIT_FOR_AUTO_CONNECT = 40
+WAIT_BEFORE_CONNECTION = 30
+
+TIMEOUT = 5
+PING_ADDR = 'www.google.com'
+
+class WifiStressTest(WifiBaseTest):
+    """WiFi Stress test class.
+
+    Test Bed Requirement:
+    * Two Android device
+    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
+      network.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        # Note that test_stress_softAP_startup_and_stop_5g will always fail
+        # when testing with a single device.
+        if len(self.android_devices) > 1:
+            self.dut_client = self.android_devices[1]
+        else:
+            self.dut_client = None
+        wutils.wifi_test_device_init(self.dut)
+        req_params = []
+        opt_param = [
+            "open_network", "reference_networks", "iperf_server_address",
+            "stress_count", "stress_hours", "attn_vals", "pno_interval",
+            "iperf_server_port"]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(ap_count=2)
+
+        asserts.assert_true(
+            len(self.reference_networks) > 0,
+            "Need at least one reference network with psk.")
+        self.wpa_2g = self.reference_networks[0]["2g"]
+        self.wpa_5g = self.reference_networks[0]["5g"]
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
+        self.networks = [self.wpa_2g, self.wpa_5g, self.open_2g, self.open_5g]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+
+    def teardown_test(self):
+        super().teardown_test()
+        if self.dut.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.dut)
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    def teardown_class(self):
+        wutils.reset_wifi(self.dut)
+        if "AccessPoint" in self.user_params:
+            del self.user_params["reference_networks"]
+            del self.user_params["open_network"]
+
+    """Helper Functions"""
+
+    def scan_and_connect_by_ssid(self, ad, network):
+        """Scan for network and connect using network information.
+
+        Args:
+            network: A dictionary representing the network to connect to.
+
+        """
+        ssid = network[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(ad, ssid)
+        wutils.wifi_connect(ad, network, num_of_tries=3)
+
+    def scan_and_connect_by_id(self, network, net_id):
+        """Scan for network and connect using network id.
+
+        Args:
+            net_id: Integer specifying the network id of the network.
+
+        """
+        ssid = network[WifiEnums.SSID_KEY]
+        wutils.start_wifi_connection_scan_and_ensure_network_found(self.dut,
+            ssid)
+        wutils.wifi_connect_by_id(self.dut, net_id)
+
+    def run_ping(self, sec):
+        """Run ping for given number of seconds.
+
+        Args:
+            sec: Time in seconds to run teh ping traffic.
+
+        """
+        self.log.info("Running ping for %d seconds" % sec)
+        result = self.dut.adb.shell("ping -w %d %s" %(sec, PING_ADDR),
+            timeout=sec+1)
+        self.log.debug("Ping Result = %s" % result)
+        if "100% packet loss" in result:
+            raise signals.TestFailure("100% packet loss during ping")
+
+    def start_youtube_video(self, url=None, secs=60):
+        """Start a youtube video and check if it's playing.
+
+        Args:
+            url: The URL of the youtube video to play.
+            secs: Time to play video in seconds.
+
+        """
+        ad = self.dut
+        ad.log.info("Start a youtube video")
+        ad.ensure_screen_on()
+        video_played = False
+        for count in range(2):
+            ad.unlock_screen()
+            ad.adb.shell('am start -a android.intent.action.VIEW -d "%s"' % url)
+            if tutils.wait_for_state(ad.droid.audioIsMusicActive, True, 15, 1):
+                ad.log.info("Started a video in youtube.")
+                # Play video for given seconds.
+                time.sleep(secs)
+                video_played = True
+                break
+        if not video_played:
+            raise signals.TestFailure("Youtube video did not start. Current WiFi "
+                "state is %d" % self.dut.droid.wifiCheckState())
+
+    def add_networks(self, ad, networks):
+        """Add Wi-Fi networks to an Android device and verify the networks were
+        added correctly.
+
+        Args:
+            ad: the AndroidDevice object to add networks to.
+            networks: a list of dicts, each dict represents a Wi-Fi network.
+        """
+        for network in networks:
+            ret = ad.droid.wifiAddNetwork(network)
+            asserts.assert_true(ret != -1, "Failed to add network %s" %
+                                network)
+            ad.droid.wifiEnableNetwork(ret, 0)
+        configured_networks = ad.droid.wifiGetConfiguredNetworks()
+        self.log.debug("Configured networks: %s", configured_networks)
+
+    def connect_and_verify_connected_ssid(self, expected_con, is_pno=False):
+        """Start a scan to get the DUT connected to an AP and verify the DUT
+        is connected to the correct SSID.
+
+        Args:
+            expected_con: The expected info of the network to we expect the DUT
+                to roam to.
+        """
+        connection_info = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Triggering network selection from %s to %s",
+                      connection_info[WifiEnums.SSID_KEY],
+                      expected_con[WifiEnums.SSID_KEY])
+        self.attenuators[0].set_atten(0)
+        if is_pno:
+            self.log.info("Wait %ss for PNO to trigger.", self.pno_interval)
+            time.sleep(self.pno_interval)
+        else:
+            # force start a single scan so we don't have to wait for the scheduled scan.
+            wutils.start_wifi_connection_scan_and_return_status(self.dut)
+            self.log.info("Wait 60s for network selection.")
+            time.sleep(60)
+        try:
+            self.log.info("Connected to %s network after network selection"
+                          % self.dut.droid.wifiGetConnectionInfo())
+            expected_ssid = expected_con[WifiEnums.SSID_KEY]
+            verify_con = {WifiEnums.SSID_KEY: expected_ssid}
+            wutils.verify_wifi_connection_info(self.dut, verify_con)
+            self.log.info("Connected to %s successfully after network selection",
+                          expected_ssid)
+        finally:
+            pass
+
+    def run_long_traffic(self, sec, args, q):
+        try:
+            # Start IPerf traffic
+            self.log.info("Running iperf client {}".format(args))
+            result, data = self.dut.run_iperf_client(self.iperf_server_address,
+                args, timeout=sec+1)
+            if not result:
+                self.log.debug("Error occurred in iPerf traffic.")
+                self.run_ping(sec)
+            q.put(True)
+        except:
+            q.put(False)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="cd0016c6-58cf-4361-b551-821c0b8d2554")
+    def test_stress_toggle_wifi_state(self):
+        """Toggle WiFi state ON and OFF for N times."""
+        for count in range(self.stress_count):
+            """Test toggling wifi"""
+            try:
+                self.log.debug("Going from on to off.")
+                wutils.wifi_toggle_state(self.dut, False)
+                self.log.debug("Going from off to on.")
+                startTime = time.time()
+                wutils.wifi_toggle_state(self.dut, True)
+                startup_time = time.time() - startTime
+                self.log.debug("WiFi was enabled on the device in %s s." %
+                    startup_time)
+            except:
+                signals.TestFailure(details="", extras={"Iterations":"%d" %
+                    self.stress_count, "Pass":"%d" %count})
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %(count+1)})
+
+    @test_tracker_info(uuid="4e591cec-9251-4d52-bc6e-6621507524dc")
+    def test_stress_toggle_wifi_state_bluetooth_on(self):
+        """Toggle WiFi state ON and OFF for N times when bluetooth ON."""
+        enable_bluetooth(self.dut.droid, self.dut.ed)
+        for count in range(self.stress_count):
+            """Test toggling wifi"""
+            try:
+                self.log.debug("Going from on to off.")
+                wutils.wifi_toggle_state(self.dut, False)
+                self.log.debug("Going from off to on.")
+                startTime = time.time()
+                wutils.wifi_toggle_state(self.dut, True)
+                startup_time = time.time() - startTime
+                self.log.debug("WiFi was enabled on the device in %s s." %
+                    startup_time)
+            except:
+                signals.TestFailure(details="", extras={"Iterations":"%d" %
+                    self.stress_count, "Pass":"%d" %count})
+        disable_bluetooth(self.dut.droid)
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %(count+1)})
+
+    @test_tracker_info(uuid="49e3916a-9580-4bf7-a60d-a0f2545dcdde")
+    def test_stress_connect_traffic_disconnect_5g(self):
+        """Test to connect and disconnect from a network for N times.
+
+           Steps:
+               1. Scan and connect to a network.
+               2. Run IPerf to upload data for few seconds.
+               3. Disconnect.
+               4. Repeat 1-3.
+
+        """
+        for count in range(self.stress_count):
+            try:
+                net_id = self.dut.droid.wifiAddNetwork(self.wpa_5g)
+                asserts.assert_true(net_id != -1, "Add network %r failed" % self.wpa_5g)
+                self.scan_and_connect_by_id(self.wpa_5g, net_id)
+                # Start IPerf traffic from phone to server.
+                # Upload data for 10s.
+                args = "-p {} -t {}".format(self.iperf_server_port, 10)
+                self.log.info("Running iperf client {}".format(args))
+                result, data = self.dut.run_iperf_client(self.iperf_server_address, args)
+                if not result:
+                    self.log.debug("Error occurred in iPerf traffic.")
+                    self.run_ping(10)
+                wutils.wifi_forget_network(self.dut,self.wpa_5g[WifiEnums.SSID_KEY])
+                time.sleep(WAIT_BEFORE_CONNECTION)
+            except:
+                raise signals.TestFailure("Network connect-disconnect failed."
+                    "Look at logs", extras={"Iterations":"%d" %
+                        self.stress_count, "Pass":"%d" %count})
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %(count+1)})
+
+    @test_tracker_info(uuid="e9827dff-0755-43ec-8b50-1f9756958460")
+    def test_stress_connect_long_traffic_5g(self):
+        """Test to connect to network and hold connection for few hours.
+
+           Steps:
+               1. Scan and connect to a network.
+               2. Run IPerf to download data for few hours.
+               3. Run IPerf to upload data for few hours.
+               4. Verify no WiFi disconnects/data interruption.
+
+        """
+        self.scan_and_connect_by_ssid(self.dut, self.wpa_5g)
+        self.scan_and_connect_by_ssid(self.dut_client, self.wpa_5g)
+
+        q = queue.Queue()
+        sec = self.stress_hours * 60 * 60
+        start_time = time.time()
+
+        dl_args = "-p {} -t {} -R".format(self.iperf_server_port, sec)
+        dl = threading.Thread(target=self.run_long_traffic, args=(sec, dl_args, q))
+        dl.start()
+        dl.join()
+
+        total_time = time.time() - start_time
+        self.log.debug("WiFi state = %d" %self.dut.droid.wifiCheckState())
+        while(q.qsize() > 0):
+            if not q.get():
+                raise signals.TestFailure("Network long-connect failed.",
+                    extras={"Total Hours":"%d" %self.stress_hours,
+                    "Seconds Run":"%d" %total_time})
+        raise signals.TestPass(details="", extras={"Total Hours":"%d" %
+            self.stress_hours, "Seconds Run":"%d" %total_time})
+
+    def test_stress_youtube_5g(self):
+        """Test to connect to network and play various youtube videos.
+
+           Steps:
+               1. Scan and connect to a network.
+               2. Loop through and play a list of youtube videos.
+               3. Verify no WiFi disconnects/data interruption.
+
+        """
+        # List of Youtube 4K videos.
+        videos = ["https://www.youtube.com/watch?v=TKmGU77INaM",
+                  "https://www.youtube.com/watch?v=WNCl-69POro",
+                  "https://www.youtube.com/watch?v=dVkK36KOcqs",
+                  "https://www.youtube.com/watch?v=0wCC3aLXdOw",
+                  "https://www.youtube.com/watch?v=rN6nlNC9WQA",
+                  "https://www.youtube.com/watch?v=RK1K2bCg4J8"]
+        try:
+            self.scan_and_connect_by_ssid(self.dut, self.wpa_5g)
+            start_time = time.time()
+            for video in videos:
+                self.start_youtube_video(url=video, secs=10*60)
+        except:
+            total_time = time.time() - start_time
+            raise signals.TestFailure("The youtube stress test has failed."
+                "WiFi State = %d" %self.dut.droid.wifiCheckState(),
+                extras={"Total Hours":"1", "Seconds Run":"%d" %total_time})
+        total_time = time.time() - start_time
+        self.log.debug("WiFi state = %d" %self.dut.droid.wifiCheckState())
+        raise signals.TestPass(details="", extras={"Total Hours":"1",
+            "Seconds Run":"%d" %total_time})
+
+    @test_tracker_info(uuid="d367c83e-5b00-4028-9ed8-f7b875997d13")
+    def test_stress_wifi_failover(self):
+        """This test does aggressive failover to several networks in list.
+
+           Steps:
+               1. Add and enable few networks.
+               2. Let device auto-connect.
+               3. Remove the connected network.
+               4. Repeat 2-3.
+               5. Device should connect to a network until all networks are
+                  exhausted.
+
+        """
+        for count in range(int(self.stress_count/4)):
+            wutils.reset_wifi(self.dut)
+            ssids = list()
+            for network in self.networks:
+                ssids.append(network[WifiEnums.SSID_KEY])
+                ret = self.dut.droid.wifiAddNetwork(network)
+                asserts.assert_true(ret != -1, "Add network %r failed" % network)
+                self.dut.droid.wifiEnableNetwork(ret, 0)
+            self.dut.droid.wifiStartScan()
+            time.sleep(WAIT_FOR_AUTO_CONNECT)
+            cur_network = self.dut.droid.wifiGetConnectionInfo()
+            cur_ssid = cur_network[WifiEnums.SSID_KEY]
+            self.log.info("Cur_ssid = %s" % cur_ssid)
+            for i in range(0,len(self.networks)):
+                self.log.debug("Forget network %s" % cur_ssid)
+                wutils.wifi_forget_network(self.dut, cur_ssid)
+                time.sleep(WAIT_FOR_AUTO_CONNECT)
+                cur_network = self.dut.droid.wifiGetConnectionInfo()
+                cur_ssid = cur_network[WifiEnums.SSID_KEY]
+                self.log.info("Cur_ssid = %s" % cur_ssid)
+                if i == len(self.networks) - 1:
+                    break
+                if cur_ssid not in ssids:
+                    raise signals.TestFailure("Device did not failover to the "
+                        "expected network. SSID = %s" % cur_ssid)
+            network_config = self.dut.droid.wifiGetConfiguredNetworks()
+            self.log.info("Network Config = %s" % network_config)
+            if len(network_config):
+                raise signals.TestFailure("All the network configurations were not "
+                    "removed. Configured networks = %s" % network_config,
+                        extras={"Iterations":"%d" % self.stress_count,
+                            "Pass":"%d" %(count*4)})
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %((count+1)*4)})
+
+    @test_tracker_info(uuid="2c19e8d1-ac16-4d7e-b309-795144e6b956")
+    def test_stress_softAP_startup_and_stop_5g(self):
+        """Test to bring up softAP and down for N times.
+
+        Steps:
+            1. Bring up softAP on 5G.
+            2. Check for softAP on teh client device.
+            3. Turn ON WiFi.
+            4. Verify softAP is turned down and WiFi is up.
+
+        """
+        ap_ssid = "softap_" + utils.rand_ascii_str(8)
+        ap_password = utils.rand_ascii_str(8)
+        self.dut.log.info("softap setup: %s %s", ap_ssid, ap_password)
+        config = {wutils.WifiEnums.SSID_KEY: ap_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = ap_password
+        # Set country code explicitly to "US".
+        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
+        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
+        for count in range(self.stress_count):
+            initial_wifi_state = self.dut.droid.wifiCheckState()
+            wutils.start_wifi_tethering(self.dut,
+                ap_ssid,
+                ap_password,
+                WifiEnums.WIFI_CONFIG_APBAND_5G)
+            wutils.start_wifi_connection_scan_and_ensure_network_found(
+                self.dut_client, ap_ssid)
+            wutils.stop_wifi_tethering(self.dut)
+            asserts.assert_false(self.dut.droid.wifiIsApEnabled(),
+                                 "SoftAp failed to shutdown!")
+            # Give some time for WiFi to come back to previous state.
+            time.sleep(2)
+            cur_wifi_state = self.dut.droid.wifiCheckState()
+            if initial_wifi_state != cur_wifi_state:
+               raise signals.TestFailure("Wifi state was %d before softAP and %d now!" %
+                    (initial_wifi_state, cur_wifi_state),
+                        extras={"Iterations":"%d" % self.stress_count,
+                            "Pass":"%d" %count})
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %(count+1)})
+
+    @test_tracker_info(uuid="eb22e26b-95d1-4580-8c76-85dfe6a42a0f")
+    def test_stress_wifi_roaming(self):
+        AP1_network = self.reference_networks[0]["5g"]
+        AP2_network = self.reference_networks[1]["5g"]
+        wutils.set_attns(self.attenuators, "AP1_on_AP2_off")
+        self.scan_and_connect_by_ssid(self.dut, AP1_network)
+        # Reduce iteration to half because each iteration does two roams.
+        for count in range(int(self.stress_count/2)):
+            self.log.info("Roaming iteration %d, from %s to %s", count,
+                           AP1_network, AP2_network)
+            try:
+                wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
+                    "AP1_off_AP2_on", AP2_network)
+                self.log.info("Roaming iteration %d, from %s to %s", count,
+                               AP2_network, AP1_network)
+                wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
+                    "AP1_on_AP2_off", AP1_network)
+            except:
+                raise signals.TestFailure("Roaming failed. Look at logs",
+                    extras={"Iterations":"%d" %self.stress_count, "Pass":"%d" %
+                        (count*2)})
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %((count+1)*2)})
+
+    @test_tracker_info(uuid="e8ae8cd2-c315-4c08-9eb3-83db65b78a58")
+    def test_stress_network_selector_2G_connection(self):
+        """
+            1. Add one saved 2G network to DUT.
+            2. Move the DUT in range.
+            3. Verify the DUT is connected to the network.
+            4. Move the DUT out of range
+            5. Repeat step 2-4
+        """
+        for attenuator in self.attenuators:
+            attenuator.set_atten(95)
+        # add a saved network to DUT
+        networks = [self.reference_networks[0]['2g']]
+        self.add_networks(self.dut, networks)
+        for count in range(self.stress_count):
+            self.connect_and_verify_connected_ssid(self.reference_networks[0]['2g'])
+            # move the DUT out of range
+            self.attenuators[0].set_atten(95)
+            time.sleep(10)
+        wutils.set_attns(self.attenuators, "default")
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %(count+1)})
+
+    @test_tracker_info(uuid="5d5d14cb-3cd1-4b3d-8c04-0d6f4b764b6b")
+    def test_stress_pno_connection_to_2g(self):
+        """Test PNO triggered autoconnect to a network for N times
+
+        Steps:
+        1. Save 2Ghz valid network configuration in the device.
+        2. Screen off DUT
+        3. Attenuate 5Ghz network and wait for a few seconds to trigger PNO.
+        4. Check the device connected to 2Ghz network automatically.
+        5. Repeat step 3-4
+        """
+        for attenuator in self.attenuators:
+            attenuator.set_atten(95)
+        # add a saved network to DUT
+        networks = [self.reference_networks[0]['2g']]
+        self.add_networks(self.dut, networks)
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        for count in range(self.stress_count):
+            self.connect_and_verify_connected_ssid(self.reference_networks[0]['2g'], is_pno=True)
+            wutils.wifi_forget_network(
+                    self.dut, networks[0][WifiEnums.SSID_KEY])
+            # move the DUT out of range
+            self.attenuators[0].set_atten(95)
+            time.sleep(10)
+            self.add_networks(self.dut, networks)
+        wutils.set_attns(self.attenuators, "default")
+        raise signals.TestPass(details="", extras={"Iterations":"%d" %
+            self.stress_count, "Pass":"%d" %(count+1)})
diff --git a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
new file mode 100644
index 0000000..6d5fef8
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
@@ -0,0 +1,310 @@
+#!/usr/bin/env python3.4
+
+import queue
+import time
+
+import acts.base_test
+import acts.test_utils.wifi.wifi_test_utils as wifi_utils
+import acts.test_utils.tel.tel_test_utils as tele_utils
+import acts.utils
+
+from acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
+from acts.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
+
+WifiEnums = wifi_utils.WifiEnums
+
+ATTENUATORS = "attenuators"
+WIFI_SSID = "wifi_network_ssid"
+WIFI_PWD = "wifi_network_pass"
+STRESS_COUNT = "stress_iteration"
+
+class WifiTeleCoexTest(TelephonyBaseTest):
+    """Tests for WiFi, Celular Co-existance."""
+
+
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+
+        self.dut = self.android_devices[0]
+        wifi_utils.wifi_test_device_init(self.dut)
+        # Set attenuation to 0 on all channels.
+        if getattr(self, ATTENUATORS, []):
+            for a in self.attenuators:
+                a.set_atten(0)
+        self.ads = self.android_devices
+        self.dut = self.android_devices[0]
+        self.wifi_network_ssid = self.user_params.get(WIFI_SSID)
+        self.wifi_network_pass = self.user_params.get(WIFI_PWD)
+        self.network = { WifiEnums.SSID_KEY : self.wifi_network_ssid,
+                         WifiEnums.PWD_KEY : self.wifi_network_pass
+                       }
+        self.stress_count = self.user_params.get(STRESS_COUNT)
+
+
+    def setup_test(self):
+        wifi_utils.wifi_toggle_state(self.dut, True)
+
+
+    def teardown_test(self):
+        wifi_utils.reset_wifi(self.dut)
+
+
+    """Helper Functions"""
+
+
+    def connect_to_wifi(self, ad, network):
+        """Connection logic for open and psk wifi networks.
+
+        Args:
+            ad: Android device object.
+            network: A JSON dict of the WiFi network configuration.
+
+        """
+        ad.ed.clear_all_events()
+        wifi_utils.start_wifi_connection_scan(ad)
+        scan_results = ad.droid.wifiGetScanResults()
+        wifi_utils.assert_network_in_list({WifiEnums.SSID_KEY:
+                self.wifi_network_ssid}, scan_results)
+        wifi_utils.wifi_connect(ad, network)
+        self.log.debug("Connected to %s network on %s device" % (
+                network[WifiEnums.SSID_KEY], ad.serial))
+
+
+    def stress_toggle_wifi(self, stress_count):
+        """Toggle WiFi in a loop.
+
+        Args:
+            stress_count: Number of times to toggle WiFi OFF and ON.
+
+        """
+        for count in range(stress_count):
+            self.log.debug("stress_toggle_wifi: Iteration %d" % count)
+            wifi_utils.toggle_wifi_off_and_on(self.dut)
+
+        if not self.dut.droid.wifiGetisWifiEnabled():
+            raise signals.TestFailure("WiFi did not turn on after toggling it"
+                                      " %d times" % self.stress_count)
+
+
+    def stress_toggle_airplane(self, stress_count):
+        """Toggle Airplane mode in a loop.
+
+        Args:
+            stress_count: Number of times to toggle Airplane mode OFF and ON.
+
+        """
+        for count in range(stress_count):
+            self.log.debug("stress_toggle_airplane: Iteration %d" % count)
+            wifi_utils.toggle_airplane_mode_on_and_off(self.dut)
+
+        if not self.dut.droid.wifiGetisWifiEnabled():
+            raise signals.TestFailure("WiFi did not turn on after toggling it"
+                                      " %d times" % self.stress_count)
+
+
+    def stress_toggle_airplane_and_wifi(self, stress_count):
+        """Toggle Airplane and WiFi modes in a loop.
+
+        Args:
+            stress_count: Number of times to perform Airplane mode ON, WiFi ON,
+                          Airplane mode OFF, in a sequence.
+
+        """
+        for count in range(stress_count):
+            self.log.debug("stress_toggle_airplane_and_wifi: Iteration %d" % count)
+            self.log.debug("Toggling Airplane mode ON")
+            asserts.assert_true(
+                acts.utils.force_airplane_mode(self.dut, True),
+                "Can not turn on airplane mode on: %s" % self.dut.serial)
+            # Sleep for atleast 500ms so that, call to enable wifi is not deferred.
+            time.sleep(1)
+            self.log.debug("Toggling wifi ON")
+            wifi_utils.wifi_toggle_state(self.dut, True)
+            # Sleep for 1s before getting new WiFi state.
+            time.sleep(1)
+            if not self.dut.droid.wifiGetisWifiEnabled():
+                raise signals.TestFailure("WiFi did not turn on after turning ON"
+                    " Airplane mode")
+            asserts.assert_true(
+                acts.utils.force_airplane_mode(self.dut, False),
+                "Can not turn on airplane mode on: %s" % self.dut.serial)
+
+        if not self.dut.droid.wifiGetisWifiEnabled():
+            raise signals.TestFailure("WiFi did not turn on after toggling it"
+                                      " %d times" % self.stress_count)
+
+
+    def setup_cellular_voice_calling(self):
+        """Setup phone for voice general calling and make sure phone is
+           attached to voice."""
+        # Make sure Phone A and B are attached to voice network.
+        for ad in self.ads:
+            if not phone_setup_voice_general(self.log, ad):
+                raise signals.TestFailure("Phone failed to setup for voice"
+                                          " calling serial:%s" % ad.serial)
+        self.log.debug("Finished setting up phones for voice calling")
+
+
+    def validate_cellular_and_wifi(self):
+        """Validate WiFi, make some cellular calls.
+
+        Steps:
+            1. Check if device is still connected to the WiFi network.
+            2. If WiFi looks good, check if deivce is attached to voice.
+            3. Make a short sequence voice call between Phone A and B.
+
+        """
+        # Sleep for 30s before getting new WiFi state.
+        time.sleep(30)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        if wifi_info[WifiEnums.SSID_KEY] != self.wifi_network_ssid:
+            raise signals.TestFailure("Phone failed to connect to %s network on"
+                                      " %s" % (self.wifi_network_ssid,
+                                      self.dut.serial))
+
+        # Make short call sequence between Phone A and Phone B.
+        two_phone_call_short_seq(self.log, self.ads[0], None, None, self.ads[1],
+                                 None, None)
+
+    """Tests"""
+
+
+    @test_tracker_info(uuid="8b9b6fb9-964b-43e7-b75f-675774ee346f")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_toggle_wifi_call(self):
+        """Test to toggle WiFi and then perform WiFi connection and
+           cellular calls.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Toggle WiFi OFF and ON.
+            4. Verify device auto-connects to the WiFi network.
+            5. Verify device is attached to voice network.
+            6. Make short sequence voice calls.
+
+        """
+        self.setup_cellular_voice_calling()
+        self.connect_to_wifi(self.dut, self.network)
+        wifi_utils.toggle_wifi_off_and_on(self.dut)
+        self.validate_cellular_and_wifi()
+        return True
+
+
+    @test_tracker_info(uuid="caf22447-6354-4a2e-99e5-0ff235fc8f20")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_toggle_airplane_call(self):
+        """Test to toggle Airplane mode and perform WiFi connection and
+           cellular calls.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Toggle Airplane mode OFF and ON.
+            4. Verify device auto-connects to the WiFi network.
+            5. Verify device is attached to voice network.
+            6. Make short sequence voice calls.
+
+        """
+        self.setup_cellular_voice_calling()
+        self.connect_to_wifi(self.dut, self.network)
+        wifi_utils.toggle_airplane_mode_on_and_off(self.dut)
+        self.validate_cellular_and_wifi()
+        return True
+
+
+    @test_tracker_info(uuid="dd888b35-f820-409a-89af-4b0f6551e4d6")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_toggle_airplane_and_wifi_call(self):
+        """Test to toggle WiFi in a loop and perform WiFi connection and
+           cellular calls.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Toggle Airplane mode ON.
+            4. Turn WiFi ON.
+            5. Toggle Airplane mode OFF.
+            3. Verify device auto-connects to the WiFi network.
+            4. Verify device is attached to voice network.
+            5. Make short sequence voice calls.
+
+        """
+        self.setup_cellular_voice_calling()
+        self.connect_to_wifi(self.dut, self.network)
+        self.stress_toggle_airplane_and_wifi(1)
+        self.validate_cellular_and_wifi()
+        return True
+
+
+    @test_tracker_info(uuid="15db5b7e-827e-4bc8-8e77-7fcce343a323")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_stress_toggle_wifi_call(self):
+        """Stress test to toggle WiFi in a loop, then perform WiFi connection
+           and cellular calls.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Toggle WiFi OFF and ON in a loop.
+            4. Verify device auto-connects to the WiFi network.
+            5. Verify device is attached to voice network.
+            6. Make short sequence voice calls.
+
+        """
+        self.setup_cellular_voice_calling()
+        self.connect_to_wifi(self.dut, self.network)
+        self.stress_toggle_wifi(self.stress_count)
+        self.validate_cellular_and_wifi()
+        return True
+
+
+    @test_tracker_info(uuid="80a2f1bf-5e41-453a-9b8e-be3b41d4d313")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_stress_toggle_airplane_call(self):
+        """Stress test to toggle Airplane mode in a loop, then perform WiFi and
+           cellular calls.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Toggle Airplane mode OFF and ON in a loop.
+            4. Verify device auto-connects to the WiFi network.
+            5. Verify device is attached to voice network.
+            6. Make short sequence voice calls.
+
+        """
+        self.setup_cellular_voice_calling()
+        self.connect_to_wifi(self.dut, self.network)
+        self.stress_toggle_airplane(self.stress_count)
+        self.validate_cellular_and_wifi()
+        return True
+
+
+    @test_tracker_info(uuid="b88ad3e7-6462-4280-ad57-22d0ac91fdd8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_stress_toggle_airplane_and_wifi_call(self):
+        """Stress test to toggle Airplane and WiFi mode in a loop, then perform
+           WiFi connection and cellular calls.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Toggle Airplane mode ON.
+            4. Turn WiFi ON.
+            5. Toggle Airplane mode OFF.
+            6. Repeat 3, 4 & 5, in a loop.
+            7. Verify device auto-connects to the WiFi network.
+            8. Verify device is attached to voice network.
+            9. Make short sequence voice calls.
+
+        """
+        self.setup_cellular_voice_calling()
+        self.connect_to_wifi(self.dut, self.network)
+        self.stress_toggle_airplane_and_wifi(self.stress_count)
+        self.validate_cellular_and_wifi()
+        return True
diff --git a/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py b/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
new file mode 100755
index 0000000..0716158
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.signals as signals
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.libs.ota import ota_updater
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+
+import acts.test_utils.net.net_test_utils as nutils
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+
+class WifiTethering2GOpenOTATest(BaseTestClass):
+    """Wifi Tethering 2G Open OTA tests"""
+
+    def setup_class(self):
+
+        super(WifiTethering2GOpenOTATest, self).setup_class()
+        ota_updater.initialize(self.user_params, self.android_devices)
+
+        self.hotspot_device = self.android_devices[0]
+        self.tethered_device = self.android_devices[1]
+        req_params = ("wifi_hotspot_open", )
+        self.unpack_userparams(req_params)
+
+        # verify hotspot device has lte data and supports tethering
+        nutils.verify_lte_data_and_tethering_supported(self.hotspot_device)
+
+        # Save a wifi soft ap configuration and verify that it works
+        wutils.save_wifi_soft_ap_config(self.hotspot_device,
+                                        self.wifi_hotspot_open,
+                                        WIFI_CONFIG_APBAND_2G)
+        self._verify_wifi_tethering()
+
+        # Run OTA below, if ota fails then abort all tests.
+        try:
+          ota_updater.update(self.hotspot_device)
+        except Exception as err:
+            raise signals.TestAbortClass(
+                "Failed up apply OTA update. Aborting tests")
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+
+    def teardown_class(self):
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Helper Functions"""
+
+    def _verify_wifi_tethering(self):
+        """Verify wifi tethering"""
+        wutils.start_wifi_tethering_saved_config(self.hotspot_device)
+        wutils.wifi_connect(self.tethered_device, self.wifi_hotspot_open)
+        # (TODO: @gmoturu) Change to stop_wifi_tethering. See b/109876061
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="fe502bc3-f9c6-4bed-98ad-acaa7e166521")
+    def test_wifi_tethering_ota_2g_open(self):
+        """ Verify wifi hotspot after ota upgrade
+
+        Steps:
+          1. Save a wifi hotspot config with 2g band and open auth
+          2. Do a OTA update
+          3. Verify that wifi hotspot works with teh saved config
+        """
+        self._verify_wifi_tethering()
diff --git a/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py b/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
new file mode 100755
index 0000000..7399e32
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.signals as signals
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.libs.ota import ota_updater
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+
+import acts.test_utils.net.net_test_utils as nutils
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+
+class WifiTethering2GPskOTATest(BaseTestClass):
+    """Wifi Tethering 2G Psk OTA tests"""
+
+    def setup_class(self):
+
+        super(WifiTethering2GPskOTATest, self).setup_class()
+        ota_updater.initialize(self.user_params, self.android_devices)
+
+        self.hotspot_device = self.android_devices[0]
+        self.tethered_device = self.android_devices[1]
+        req_params = ("wifi_hotspot_psk", )
+        self.unpack_userparams(req_params)
+
+        # verify hotspot device has lte data and supports tethering
+        nutils.verify_lte_data_and_tethering_supported(self.hotspot_device)
+
+        # Save a wifi soft ap configuration and verify that it works
+        wutils.save_wifi_soft_ap_config(self.hotspot_device,
+                                        self.wifi_hotspot_psk,
+                                        WIFI_CONFIG_APBAND_2G)
+        self._verify_wifi_tethering()
+
+        # Run OTA below, if ota fails then abort all tests.
+        try:
+          ota_updater.update(self.hotspot_device)
+        except Exception as err:
+            raise signals.TestAbortClass(
+                "Failed up apply OTA update. Aborting tests")
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+
+    def teardown_class(self):
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Helper Functions"""
+
+    def _verify_wifi_tethering(self):
+        """Verify wifi tethering"""
+        wutils.start_wifi_tethering_saved_config(self.hotspot_device)
+        wutils.wifi_connect(self.tethered_device, self.wifi_hotspot_psk)
+        # (TODO: @gmoturu) Change to stop_wifi_tethering. See b/109876061
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="4b1cec63-d1d2-4046-84e9-e806bb08ce41")
+    def test_wifi_tethering_ota_2g_psk(self):
+        """ Verify wifi hotspot after ota upgrade
+
+        Steps:
+          1. Save a wifi hotspot config with 2g band and psk auth
+          2. Do a OTA update
+          3. Verify that wifi hotspot works with teh saved config
+        """
+        self._verify_wifi_tethering()
diff --git a/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py b/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
new file mode 100755
index 0000000..985e7a7
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.signals as signals
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.libs.ota import ota_updater
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+
+import acts.test_utils.net.net_test_utils as nutils
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+
+class WifiTethering5GOpenOTATest(BaseTestClass):
+    """Wifi Tethering 5G Open OTA tests"""
+
+    def setup_class(self):
+
+        super(WifiTethering5GOpenOTATest, self).setup_class()
+        ota_updater.initialize(self.user_params, self.android_devices)
+
+        self.hotspot_device = self.android_devices[0]
+        self.tethered_device = self.android_devices[1]
+        req_params = ("wifi_hotspot_open", )
+        self.unpack_userparams(req_params)
+
+        # verify hotspot device has lte data and supports tethering
+        nutils.verify_lte_data_and_tethering_supported(self.hotspot_device)
+
+        # Save a wifi soft ap configuration and verify that it works
+        wutils.save_wifi_soft_ap_config(self.hotspot_device,
+                                        self.wifi_hotspot_open,
+                                        WIFI_CONFIG_APBAND_5G)
+        self._verify_wifi_tethering()
+
+        # Run OTA below, if ota fails then abort all tests.
+        try:
+          ota_updater.update(self.hotspot_device)
+        except Exception as err:
+            raise signals.TestAbortClass(
+                "Failed up apply OTA update. Aborting tests")
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+
+    def teardown_class(self):
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Helper Functions"""
+
+    def _verify_wifi_tethering(self):
+        """Verify wifi tethering"""
+        wutils.start_wifi_tethering_saved_config(self.hotspot_device)
+        wutils.wifi_connect(self.tethered_device, self.wifi_hotspot_open)
+        # (TODO: @gmoturu) Change to stop_wifi_tethering. See b/109876061
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="b1a94c8f-f3ed-4755-be4a-764e205b4483")
+    def test_wifi_tethering_ota_5g_open(self):
+        """ Verify wifi hotspot after ota upgrade
+
+        Steps:
+          1. Save a wifi hotspot config with 5g band and open auth
+          2. Do a OTA update
+          3. Verify that wifi hotspot works with teh saved config
+        """
+        self._verify_wifi_tethering()
diff --git a/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py b/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
new file mode 100755
index 0000000..9e68f22
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.signals as signals
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.libs.ota import ota_updater
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+
+import acts.test_utils.net.net_test_utils as nutils
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+
+class WifiTethering5GPskOTATest(BaseTestClass):
+    """Wifi Tethering 5G Psk OTA tests"""
+
+    def setup_class(self):
+
+        super(WifiTethering5GPskOTATest, self).setup_class()
+        ota_updater.initialize(self.user_params, self.android_devices)
+
+        self.hotspot_device = self.android_devices[0]
+        self.tethered_device = self.android_devices[1]
+        req_params = ("wifi_hotspot_psk", )
+        self.unpack_userparams(req_params)
+
+        # verify hotspot device has lte data and supports tethering
+        nutils.verify_lte_data_and_tethering_supported(self.hotspot_device)
+
+        # Save a wifi soft ap configuration and verify that it works
+        wutils.save_wifi_soft_ap_config(self.hotspot_device,
+                                        self.wifi_hotspot_psk,
+                                        WIFI_CONFIG_APBAND_5G)
+        self._verify_wifi_tethering()
+
+        # Run OTA below, if ota fails then abort all tests.
+        try:
+          ota_updater.update(self.hotspot_device)
+        except Exception as err:
+            raise signals.TestAbortClass(
+                "Failed up apply OTA update. Aborting tests")
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+
+    def teardown_class(self):
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Helper Functions"""
+
+    def _verify_wifi_tethering(self):
+        """Verify wifi tethering"""
+        wutils.start_wifi_tethering_saved_config(self.hotspot_device)
+        wutils.wifi_connect(self.tethered_device, self.wifi_hotspot_psk)
+        # (TODO: @gmoturu) Change to stop_wifi_tethering. See b/109876061
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    """Tests"""
+
+    @test_tracker_info(uuid="111d3a33-3152-4993-b1ba-307daaf2a6ff")
+    def test_wifi_tethering_ota_5g_psk(self):
+        """ Verify wifi hotspot after ota upgrade
+
+        Steps:
+          1. Save a wifi hotspot config with 5g band and psk auth
+          2. Do a OTA update
+          3. Verify that wifi hotspot works with teh saved config
+        """
+        self._verify_wifi_tethering()
diff --git a/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py b/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
new file mode 100644
index 0000000..dd3cf74
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import threading
+import time
+
+from acts import base_test
+from acts import asserts
+from acts.controllers import adb
+from acts.controllers import monsoon
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.tel import tel_data_utils as tel_utils
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.tel.tel_test_utils import http_file_download_by_chrome
+from acts.utils import force_airplane_mode
+from acts.utils import set_adaptive_brightness
+from acts.utils import set_ambient_display
+from acts.utils import set_auto_rotate
+from acts.utils import set_location_service
+
+
+class WifiTetheringPowerTest(base_test.BaseTestClass):
+
+    def setup_class(self):
+        self.hotspot_device = self.android_devices[0]
+        self.tethered_devices = self.android_devices[1:]
+        req_params = ("ssid", "password", "url")
+        self.unpack_userparams(req_params)
+        self.network = { "SSID": self.ssid, "password": self.password }
+
+        self.offset = 1 * 60
+        self.hz = 5000
+        self.duration = 9 * 60 + self.offset
+        self.mon_data_path = os.path.join(self.log_path, "Monsoon")
+        self.mon = self.monsoons[0]
+        self.mon.set_voltage(4.2)
+        self.mon.set_max_current(7.8)
+        self.mon.attach_device(self.hotspot_device)
+
+        asserts.assert_true(self.mon.usb("auto"),
+            "Failed to turn USB mode to auto on monsoon.")
+        set_location_service(self.hotspot_device, False)
+        set_adaptive_brightness(self.hotspot_device, False)
+        set_ambient_display(self.hotspot_device, False)
+        self.hotspot_device.adb.shell("settings put system screen_brightness 0")
+        set_auto_rotate(self.hotspot_device, False)
+        wutils.wifi_toggle_state(self.hotspot_device, False)
+        self.hotspot_device.droid.telephonyToggleDataConnection(True)
+        tel_utils.wait_for_cell_data_connection(self.log, self.hotspot_device, True)
+        asserts.assert_true(
+            tel_utils.verify_http_connection(self.log, self.hotspot_device),
+            "HTTP verification failed on cell data connection")
+        for ad in self.tethered_devices:
+            wutils.reset_wifi(ad)
+
+    def teardown_class(self):
+        self.mon.usb("on")
+        wutils.wifi_toggle_state(self.hotspot_device, True)
+
+    def on_fail(self, test_name, begin_time):
+        self.hotspot_device.take_bug_report(test_name, begin_time)
+
+    def on_pass(self, test_name, begin_time):
+        self.hotspot_device.take_bug_report(test_name, begin_time)
+
+    """ Helper functions """
+    def _measure_and_process_result(self):
+        """ Measure the current drawn by the device for the period of
+            self.duration, at the frequency of self.hz.
+        """
+        tag = self.current_test_name
+        result = self.mon.measure_power(self.hz,
+                                        self.duration,
+                                        tag=tag,
+                                        offset=self.offset)
+        asserts.assert_true(result,
+                            "Got empty measurement data set in %s." % tag)
+        self.log.info(repr(result))
+        data_path = os.path.join(self.mon_data_path, "%s.txt" % tag)
+        monsoon.MonsoonData.save_to_text_file([result], data_path)
+        actual_current = result.average_current
+        actual_current_str = "%.2fmA" % actual_current
+        result_extra = {"Average Current": actual_current_str}
+
+    def _start_wifi_tethering(self, wifi_band):
+        """ Start wifi tethering on hotspot device
+
+            Args:
+              1. wifi_band: specifies the wifi band to start the hotspot
+              on. The current options are 2G and 5G
+        """
+        wutils.start_wifi_tethering(self.hotspot_device,
+                                    self.ssid,
+                                    self.password,
+                                    wifi_band)
+
+    def _start_traffic_on_device(self, ad):
+        """ Start traffic on the device by downloading data
+            Run the traffic continuosly for self.duration
+
+            Args:
+              1. ad device object
+        """
+        timeout = time.time() + self.duration
+        while True:
+            if time.time() > timeout:
+                break
+            http_file_download_by_chrome(ad, self.url)
+
+    def _start_traffic_measure_power(self, ad_list):
+        """ Start traffic on the tethered devices and measure power
+
+            Args:
+              1. ad_list: list of tethered devices to run traffic on
+        """
+        threads = []
+        for ad in ad_list:
+            t = threading.Thread(target = self._start_traffic_on_device,
+                                 args = (ad,))
+            t.start()
+            threads.append(t)
+        try:
+            self._measure_and_process_result()
+        finally:
+            for t in threads:
+                t.join()
+
+
+    """ Tests begin """
+    @test_tracker_info(uuid="ebb74144-e22a-46e1-b8c1-9ada22b13133")
+    def test_power_wifi_tethering_2ghz_no_devices_connected(self):
+        """ Steps:
+              1. Start wifi hotspot with 2.4Ghz band
+              2. No devices connected to hotspot
+              3. Measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        self._measure_and_process_result()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="2560c088-4010-4354-ade3-6aaac83b1cfd")
+    def test_power_wifi_tethering_5ghz_no_devices_connected(self):
+        """ Steps:
+              1. Start wifi hotspot with 5Ghz band
+              2. No devices connected to hotspot
+              3. Measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        self._measure_and_process_result()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="644795b0-cd30-4a8f-82ee-cc0618c41c6b")
+    def test_power_wifi_tethering_2ghz_connect_1device(self):
+        """ Steps:
+              1. Start wifi hotspot with 2.4GHz band
+              2. Connect 1 device to hotspot
+              3. Measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        wutils.wifi_connect(self.tethered_devices[0], self.network)
+        self._measure_and_process_result()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="8fca9898-f493-44c3-810f-d2262ac72187")
+    def test_power_wifi_tethering_5ghz_connect_1device(self):
+        """ Steps:
+              1. Start wifi hotspot with 5GHz band
+              2. Connect 1 device to hotspot
+              3. Measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        wutils.wifi_connect(self.tethered_devices[0], self.network)
+        self._measure_and_process_result()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="16ef5f63-1a7a-44ae-bf8d-c3a181c89b63")
+    def test_power_wifi_tethering_2ghz_connect_5devices(self):
+        """ Steps:
+              1. Start wifi hotspot with 2GHz band
+              2. Connect 5 devices to hotspot
+              3. Measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        for ad in self.tethered_devices:
+            wutils.wifi_connect(ad, self.network)
+        self._measure_and_process_result()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="769aedfc-d309-40e0-95dd-51ff40f4e097")
+    def test_power_wifi_tethering_5ghz_connect_5devices(self):
+        """ Steps:
+              1. Start wifi hotspot with 5GHz band
+              2. Connect 5 devices to hotspot
+              3. Measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        for ad in self.tethered_devices:
+            wutils.wifi_connect(ad, self.network)
+        self._measure_and_process_result()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="e5b71f34-1dc0-4045-a45e-48c1e9426ec3")
+    def test_power_wifi_tethering_2ghz_connect_1device_with_traffic(self):
+        """ Steps:
+              1. Start wifi hotspot with 2GHz band
+              2. Connect 1 device to hotspot device
+              3. Start traffic and measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        wutils.wifi_connect(self.tethered_devices[0], self.network)
+        self._start_traffic_measure_power(self.tethered_devices[0:1])
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="29c5cd6e-8df1-46e5-a735-526dc9154f6e")
+    def test_power_wifi_tethering_5ghz_connect_1device_with_traffic(self):
+        """ Steps:
+              1. Start wifi hotspot with 5GHz band
+              2. Connect 1 device to hotspot device
+              3. Start traffic and measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        wutils.wifi_connect(self.tethered_devices[0], self.network)
+        self._start_traffic_measure_power(self.tethered_devices[0:1])
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="da71b06f-7b98-4c14-a2e2-361f395b39a8")
+    def test_power_wifi_tethering_2ghz_connect_5devices_with_traffic(self):
+        """ Steps:
+              1. Start wifi hotspot with 2GHz band
+              2. Connect 5 devices to hotspot device
+              3. Start traffic and measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        for ad in self.tethered_devices:
+            wutils.wifi_connect(ad, self.network)
+        self._start_traffic_measure_power(self.tethered_devices)
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="7f3173ab-fd8f-4579-8c45-f9a8c5cd17f7")
+    def test_power_wifi_tethering_5ghz_connect_5devices_with_traffic(self):
+        """ Steps:
+              1. Start wifi hotspot with 2GHz band
+              2. Connect 5 devices to hotspot device
+              3. Start traffic and measure power
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        for ad in self.tethered_devices:
+            wutils.wifi_connect(ad, self.network)
+        self._start_traffic_measure_power(self.tethered_devices)
+        wutils.stop_wifi_tethering(self.hotspot_device)
diff --git a/acts_tests/tests/google/wifi/WifiTetheringTest.py b/acts_tests/tests/google/wifi/WifiTetheringTest.py
new file mode 100644
index 0000000..56c6427
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiTetheringTest.py
@@ -0,0 +1,590 @@
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import random
+import socket
+import time
+
+from acts import asserts
+from acts import test_runner
+from acts import utils
+from acts.controllers import adb
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel import tel_defines
+from acts.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
+from acts.test_utils.tel.tel_test_utils import get_operator_name
+from acts.test_utils.tel.tel_test_utils import verify_http_connection
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.net import socket_test_utils as sutils
+from acts.test_utils.net import arduino_test_utils as dutils
+from acts.test_utils.net import net_test_utils as nutils
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WAIT_TIME = 5
+
+class WifiTetheringTest(WifiBaseTest):
+    """ Tests for Wifi Tethering """
+
+    def setup_class(self):
+        """ Setup devices for tethering and unpack params """
+        super().setup_class()
+        self.hotspot_device = self.android_devices[0]
+        self.tethered_devices = self.android_devices[1:]
+        req_params = ("url", "open_network")
+        self.unpack_userparams(req_params)
+        self.network = {"SSID": "hotspot_%s" % utils.rand_ascii_str(6),
+                        "password": "pass_%s" % utils.rand_ascii_str(6)}
+        self.new_ssid = "hs_%s" % utils.rand_ascii_str(6)
+
+        nutils.verify_lte_data_and_tethering_supported(self.hotspot_device)
+        for ad in self.tethered_devices:
+            wutils.wifi_test_device_init(ad)
+
+    def setup_test(self):
+        super().setup_test()
+        self.tethered_devices[0].droid.telephonyToggleDataConnection(False)
+
+    def teardown_test(self):
+        super().teardown_test()
+        if self.hotspot_device.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.hotspot_device)
+        self.tethered_devices[0].droid.telephonyToggleDataConnection(True)
+
+    def teardown_class(self):
+        """ Reset devices """
+        for ad in self.tethered_devices:
+            wutils.reset_wifi(ad)
+
+    """ Helper functions """
+
+    def _is_ipaddress_ipv6(self, ip_address):
+        """ Verify if the given string is a valid IPv6 address
+
+        Args:
+            1. string which contains the IP address
+
+        Returns:
+            True: if valid ipv6 address
+            False: if not
+        """
+        try:
+            socket.inet_pton(socket.AF_INET6, ip_address)
+            return True
+        except socket.error:
+            return False
+
+    def _supports_ipv6_tethering(self, dut):
+        """ Check if provider supports IPv6 tethering.
+            Currently, only Verizon supports IPv6 tethering
+
+        Returns:
+            True: if provider supports IPv6 tethering
+            False: if not
+        """
+        # Currently only Verizon support IPv6 tethering
+        carrier_supports_tethering = ["vzw", "tmo", "Far EasTone", "Chunghwa Telecom"]
+        operator = get_operator_name(self.log, dut)
+        return operator in carrier_supports_tethering
+
+    def _carrier_supports_ipv6(self,dut):
+        """ Verify if carrier supports ipv6
+            Currently, only verizon and t-mobile supports IPv6
+
+        Returns:
+            True: if carrier supports ipv6
+            False: if not
+        """
+        carrier_supports_ipv6 = ["vzw", "tmo", "Far EasTone", "Chunghwa Telecom"]
+        operator = get_operator_name(self.log, dut)
+        self.log.info("Carrier is %s" % operator)
+        return operator in carrier_supports_ipv6
+
+    def _verify_ipv6_tethering(self, dut):
+        """ Verify IPv6 tethering """
+        http_response = dut.droid.httpRequestString(self.url)
+        self.log.info("IP address %s " % http_response)
+        active_link_addrs = dut.droid.connectivityGetAllAddressesOfActiveLink()
+        if dut==self.hotspot_device and self._carrier_supports_ipv6(dut)\
+            or self._supports_ipv6_tethering(self.hotspot_device):
+            asserts.assert_true(self._is_ipaddress_ipv6(http_response),
+                                "The http response did not return IPv6 address")
+            asserts.assert_true(
+                active_link_addrs and http_response in str(active_link_addrs),
+                "Could not find IPv6 address in link properties")
+            asserts.assert_true(
+                dut.droid.connectivityHasIPv6DefaultRoute(),
+                "Could not find IPv6 default route in link properties")
+        else:
+            asserts.assert_true(
+                not dut.droid.connectivityHasIPv6DefaultRoute(),
+                "Found IPv6 default route in link properties")
+
+    def _start_wifi_tethering(self, wifi_band=WIFI_CONFIG_APBAND_2G):
+        """ Start wifi tethering on hotspot device
+
+        Args:
+            1. wifi_band: specifies the wifi band to start the hotspot
+               on. The current options are 2G and 5G
+        """
+        wutils.start_wifi_tethering(self.hotspot_device,
+                                    self.network[wutils.WifiEnums.SSID_KEY],
+                                    self.network[wutils.WifiEnums.PWD_KEY],
+                                    wifi_band)
+
+    def _connect_disconnect_devices(self):
+        """ Randomly connect and disconnect devices from the
+            self.tethered_devices list to hotspot device
+        """
+        device_connected = [ False ] * len(self.tethered_devices)
+        for _ in range(50):
+            dut_id = random.randint(0, len(self.tethered_devices)-1)
+            dut = self.tethered_devices[dut_id]
+            # wait for 1 sec between connect & disconnect stress test
+            time.sleep(1)
+            if device_connected[dut_id]:
+                wutils.wifi_forget_network(dut, self.network["SSID"])
+            else:
+                wutils.wifi_connect(dut, self.network)
+            device_connected[dut_id] = not device_connected[dut_id]
+
+    def _connect_disconnect_android_device(self, dut_id, wifi_state):
+        """ Connect or disconnect wifi on android device depending on the
+            current wifi state
+
+        Args:
+            1. dut_id: tethered device to change the wifi state
+            2. wifi_state: current wifi state
+        """
+        ad = self.tethered_devices[dut_id]
+        if wifi_state:
+            self.log.info("Disconnecting wifi on android device")
+            wutils.wifi_forget_network(ad, self.network["SSID"])
+        else:
+            self.log.info("Connecting to wifi on android device")
+            wutils.wifi_connect(ad, self.network)
+
+    def _connect_disconnect_wifi_dongle(self, dut_id, wifi_state):
+        """ Connect or disconnect wifi on wifi dongle depending on the
+            current wifi state
+
+        Args:
+            1. dut_id: wifi dongle to change the wifi state
+            2. wifi_state: current wifi state
+        """
+        wd = self.arduino_wifi_dongles[dut_id]
+        if wifi_state:
+            self.log.info("Disconnecting wifi on dongle")
+            dutils.disconnect_wifi(wd)
+        else:
+            self.log.info("Connecting to wifi on dongle")
+            dutils.connect_wifi(wd, self.network)
+
+    def _connect_disconnect_tethered_devices(self):
+        """ Connect disconnect tethered devices to wifi hotspot """
+        num_android_devices = len(self.tethered_devices)
+        num_wifi_dongles = 0
+        if hasattr(self, 'arduino_wifi_dongles'):
+            num_wifi_dongles = len(self.arduino_wifi_dongles)
+        total_devices = num_android_devices + num_wifi_dongles
+        device_connected = [False] * total_devices
+        for _ in range(50):
+            dut_id = random.randint(0, total_devices-1)
+            wifi_state = device_connected[dut_id]
+            if dut_id < num_android_devices:
+              self._connect_disconnect_android_device(dut_id, wifi_state)
+            else:
+              self._connect_disconnect_wifi_dongle(dut_id-num_android_devices,
+                                                   wifi_state)
+            device_connected[dut_id] = not device_connected[dut_id]
+
+    def _verify_ping(self, dut, ip, isIPv6=False):
+        """ Verify ping works from the dut to IP/hostname
+
+        Args:
+            1. dut - ad object to check ping from
+            2. ip - ip/hostname to ping (IPv4 and IPv6)
+
+        Returns:
+            True - if ping is successful
+            False - if not
+        """
+        self.log.info("Pinging %s from dut %s" % (ip, dut.serial))
+        if isIPv6 or self._is_ipaddress_ipv6(ip):
+            return dut.droid.pingHost(ip, 5, "ping6")
+        return dut.droid.pingHost(ip)
+
+    def _return_ip_for_interface(self, dut, iface_name):
+        """ Return list of IP addresses for an interface
+
+        Args:
+            1. dut - ad object
+            2. iface_name - interface name
+
+        Returns:
+            List of IPv4 and IPv6 addresses
+        """
+        return dut.droid.connectivityGetIPv4Addresses(iface_name) + \
+            dut.droid.connectivityGetIPv6Addresses(iface_name)
+
+    def _test_traffic_between_two_tethered_devices(self, ad, wd):
+        """ Verify pinging interfaces of one DUT from another
+
+        Args:
+            1. ad - android device
+            2. wd - wifi dongle
+        """
+        wutils.wifi_connect(ad, self.network)
+        dutils.connect_wifi(wd, self.network)
+        local_ip = ad.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        remote_ip = wd.ip_address()
+        port = 8888
+
+        time.sleep(6) # wait until UDP packets method is invoked
+        socket = sutils.open_datagram_socket(ad, local_ip, port)
+        sutils.send_recv_data_datagram_sockets(
+            ad, ad, socket, socket, remote_ip, port)
+        sutils.close_datagram_socket(ad, socket)
+
+    def _ping_hotspot_interfaces_from_tethered_device(self, dut):
+        """ Ping hotspot interfaces from tethered device
+
+        Args:
+            1. dut - tethered device
+
+        Returns:
+            True - if all IP addresses are pingable
+            False - if not
+        """
+        ifaces = self.hotspot_device.droid.connectivityGetNetworkInterfaces()
+        return_result = True
+        for interface in ifaces:
+            iface_name = interface.split()[0].split(':')[1]
+            if iface_name == "lo":
+                continue
+            ip_list = self._return_ip_for_interface(
+                self.hotspot_device, iface_name)
+            for ip in ip_list:
+                ping_result = self._verify_ping(dut, ip)
+                self.log.info("Ping result: %s %s %s" %
+                              (iface_name, ip, ping_result))
+                return_result = return_result and ping_result
+
+        return return_result
+
+    def _save_wifi_softap_configuration(self, ad, config):
+        """ Save soft AP configuration
+
+        Args:
+            1. dut - device to save configuration on
+            2. config - soft ap configuration
+        """
+        asserts.assert_true(ad.droid.wifiSetWifiApConfiguration(config),
+                            "Failed to set WifiAp Configuration")
+        wifi_ap = ad.droid.wifiGetApConfiguration()
+        asserts.assert_true(wifi_ap[wutils.WifiEnums.SSID_KEY] == config[wutils.WifiEnums.SSID_KEY],
+                            "Configured wifi hotspot SSID does not match with the expected SSID")
+
+    def _turn_on_wifi_hotspot(self, ad):
+        """ Turn on wifi hotspot with a config that is already saved
+
+        Args:
+            1. dut - device to turn wifi hotspot on
+        """
+        ad.droid.wifiStartTrackingTetherStateChange()
+        ad.droid.connectivityStartTethering(tel_defines.TETHERING_WIFI, False)
+        try:
+            ad.ed.pop_event("ConnectivityManagerOnTetheringStarted")
+            ad.ed.wait_for_event("TetherStateChanged",
+                                  lambda x: x["data"]["ACTIVE_TETHER"], 30)
+        except:
+            asserts.fail("Didn't receive wifi tethering starting confirmation")
+        ad.droid.wifiStopTrackingTetherStateChange()
+
+    """ Test Cases """
+
+    @test_tracker_info(uuid="36d03295-bea3-446e-8342-b9f8f1962a32")
+    def test_ipv6_tethering(self):
+        """ IPv6 tethering test
+
+        Steps:
+            1. Start wifi tethering on hotspot device
+            2. Verify IPv6 address on hotspot device (VZW & TMO only)
+            3. Connect tethered device to hotspot device
+            4. Verify IPv6 address on the client's link properties (VZW only)
+            5. Verify ping on client using ping6 which should pass (VZW only)
+            6. Disable mobile data on provider and verify that link properties
+               does not have IPv6 address and default route (VZW only)
+        """
+        # Start wifi tethering on the hotspot device
+        wutils.toggle_wifi_off_and_on(self.hotspot_device)
+        self._start_wifi_tethering()
+
+        # Verify link properties on hotspot device
+        self.log.info("Check IPv6 properties on the hotspot device. "
+                      "Verizon & T-mobile should have IPv6 in link properties")
+        self._verify_ipv6_tethering(self.hotspot_device)
+
+        # Connect the client to the SSID
+        wutils.wifi_connect(self.tethered_devices[0], self.network)
+
+        # Need to wait atleast 2 seconds for IPv6 address to
+        # show up in the link properties
+        time.sleep(WAIT_TIME)
+
+        # Verify link properties on tethered device
+        self.log.info("Check IPv6 properties on the tethered device. "
+                      "Device should have IPv6 if carrier is Verizon")
+        self._verify_ipv6_tethering(self.tethered_devices[0])
+
+        # Verify ping6 on tethered device
+        ping_result = self._verify_ping(self.tethered_devices[0],
+                                        wutils.DEFAULT_PING_ADDR, True)
+        if self._supports_ipv6_tethering(self.hotspot_device):
+            asserts.assert_true(ping_result, "Ping6 failed on the client")
+        else:
+            asserts.assert_true(not ping_result, "Ping6 failed as expected")
+
+        # Disable mobile data on hotspot device
+        # and verify the link properties on tethered device
+        self.log.info("Disabling mobile data to verify ipv6 default route")
+        self.hotspot_device.droid.telephonyToggleDataConnection(False)
+        asserts.assert_equal(
+            self.hotspot_device.droid.telephonyGetDataConnectionState(),
+            tel_defines.DATA_STATE_CONNECTED,
+            "Could not disable cell data")
+
+        time.sleep(WAIT_TIME) # wait until the IPv6 is removed from link properties
+
+        result = self.tethered_devices[0].droid.connectivityHasIPv6DefaultRoute()
+        self.hotspot_device.droid.telephonyToggleDataConnection(True)
+        if result:
+            asserts.fail("Found IPv6 default route in link properties:Data off")
+        self.log.info("Did not find IPv6 address in link properties")
+
+        # Disable wifi tethering
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="110b61d1-8af2-4589-8413-11beac7a3025")
+    def test_wifi_tethering_2ghz_traffic_between_2tethered_devices(self):
+        """ Steps:
+
+            1. Start wifi hotspot with 2G band
+            2. Connect 2 tethered devices to the hotspot device
+            3. Ping interfaces between the tethered devices
+        """
+        asserts.skip_if(not hasattr(self, 'arduino_wifi_dongles'),
+                        "No wifi dongles connected. Skipping test")
+        wutils.toggle_wifi_off_and_on(self.hotspot_device)
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        self._test_traffic_between_two_tethered_devices(self.tethered_devices[0],
+                                                        self.arduino_wifi_dongles[0])
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="953f6e2e-27bd-4b73-85a6-d2eaa4e755d5")
+    def wifi_tethering_5ghz_traffic_between_2tethered_devices(self):
+        """ Steps:
+
+            1. Start wifi hotspot with 5ghz band
+            2. Connect 2 tethered devices to the hotspot device
+            3. Send traffic between the tethered devices
+        """
+        wutils.toggle_wifi_off_and_on(self.hotspot_device)
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        self._test_traffic_between_two_tethered_devices(self.tethered_devices[0],
+                                                        self.arduino_wifi_dongles[0])
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="d7d5aa51-682d-4882-a334-61966d93b68c")
+    def test_wifi_tethering_2ghz_connect_disconnect_devices(self):
+        """ Steps:
+
+            1. Start wifi hotspot with 2ghz band
+            2. Connect and disconnect multiple devices randomly
+            3. Verify the correct functionality
+        """
+        wutils.toggle_wifi_off_and_on(self.hotspot_device)
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+        self._connect_disconnect_tethered_devices()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="34abd6c9-c7f1-4d89-aa2b-a66aeabed9aa")
+    def test_wifi_tethering_5ghz_connect_disconnect_devices(self):
+        """ Steps:
+
+            1. Start wifi hotspot with 5ghz band
+            2. Connect and disconnect multiple devices randomly
+            3. Verify the correct functionality
+        """
+        wutils.toggle_wifi_off_and_on(self.hotspot_device)
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        self._connect_disconnect_devices()
+        wutils.stop_wifi_tethering(self.hotspot_device)
+
+    @test_tracker_info(uuid="7edfb220-37f8-42ea-8d7c-39712fbe9be5")
+    def test_wifi_tethering_wpapsk_network_2g(self):
+        """ Steps:
+
+            1. Start wifi tethering with wpapsk network 2G band
+            2. Connect tethered device to the SSID
+            3. Verify internet connectivity
+        """
+        self._start_wifi_tethering()
+        wutils.connect_to_wifi_network(self.tethered_devices[0],
+                                       self.network,
+                                       check_connectivity=True)
+
+    @test_tracker_info(uuid="17e450f4-795f-4e67-adab-984940dffedc")
+    def test_wifi_tethering_wpapsk_network_5g(self):
+        """ Steps:
+
+            1. Start wifi tethering with wpapsk network 5G band
+            2. Connect tethered device to the SSID
+            3. Verify internet connectivity
+        """
+        self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+        wutils.connect_to_wifi_network(self.tethered_devices[0],
+                                       self.network,
+                                       check_connectivity=True)
+
+    @test_tracker_info(uuid="2bc344cb-0277-4f06-b6cc-65b3972086ed")
+    def test_change_wifi_hotspot_ssid_when_hotspot_enabled(self):
+        """ Steps:
+
+            1. Start wifi tethering
+            2. Verify wifi Ap configuration
+            3. Change the SSID of the wifi hotspot while hotspot is on
+            4. Verify the new SSID in wifi ap configuration
+            5. Restart tethering and verify that the tethered device is able
+               to connect to the new SSID
+        """
+        dut = self.hotspot_device
+
+        # start tethering and verify the wifi ap configuration settings
+        self._start_wifi_tethering()
+        wifi_ap = dut.droid.wifiGetApConfiguration()
+        asserts.assert_true(
+            wifi_ap[wutils.WifiEnums.SSID_KEY] == \
+                self.network[wutils.WifiEnums.SSID_KEY],
+            "Configured wifi hotspot SSID did not match with the expected SSID")
+        wutils.connect_to_wifi_network(self.tethered_devices[0], self.network)
+
+        # update the wifi ap configuration with new ssid
+        config = {wutils.WifiEnums.SSID_KEY: self.new_ssid}
+        config[wutils.WifiEnums.PWD_KEY] = self.network[wutils.WifiEnums.PWD_KEY]
+        config[wutils.WifiEnums.AP_BAND_KEY] = WIFI_CONFIG_APBAND_2G
+        self._save_wifi_softap_configuration(dut, config)
+
+        # start wifi tethering with new wifi ap configuration
+        wutils.stop_wifi_tethering(dut)
+        self._turn_on_wifi_hotspot(dut)
+
+        # verify dut can connect to new wifi ap configuration
+        new_network = {wutils.WifiEnums.SSID_KEY: self.new_ssid,
+                       wutils.WifiEnums.PWD_KEY: \
+                       self.network[wutils.WifiEnums.PWD_KEY]}
+        wutils.connect_to_wifi_network(self.tethered_devices[0], new_network)
+
+    @test_tracker_info(uuid="4cf7ab26-ca2d-46f6-9d3f-a935c7e04c97")
+    def test_wifi_tethering_open_network_2g(self):
+        """ Steps:
+
+            1. Start wifi tethering with open network 2G band
+               (Not allowed manually. b/72412729)
+            2. Connect tethered device to the SSID
+            3. Verify internet connectivity
+        """
+        open_network = {
+            wutils.WifiEnums.SSID_KEY: "hs_2g_%s" % utils.rand_ascii_str(6)
+        }
+        wutils.start_wifi_tethering(
+            self.hotspot_device,
+            open_network[wutils.WifiEnums.SSID_KEY],
+            None,
+            WIFI_CONFIG_APBAND_2G)
+        wutils.connect_to_wifi_network(self.tethered_devices[0],
+                                       open_network,
+                                       check_connectivity=True)
+
+    @test_tracker_info(uuid="f407049b-1324-40ea-a8d1-f90587933310")
+    def test_wifi_tethering_open_network_5g(self):
+        """ Steps:
+
+            1. Start wifi tethering with open network 5G band
+               (Not allowed manually. b/72412729)
+            2. Connect tethered device to the SSID
+            3. Verify internet connectivity
+        """
+        open_network = {
+            wutils.WifiEnums.SSID_KEY: "hs_5g_%s" % utils.rand_ascii_str(6)
+        }
+        wutils.start_wifi_tethering(
+            self.hotspot_device,
+            open_network[wutils.WifiEnums.SSID_KEY],
+            None,
+            WIFI_CONFIG_APBAND_5G)
+        wutils.connect_to_wifi_network(self.tethered_devices[0],
+                                       open_network,
+                                       check_connectivity=True)
+
+    @test_tracker_info(uuid="d964f2e6-0acb-417c-ada9-eb9fc5a470e4")
+    def test_wifi_tethering_open_network_2g_stress(self):
+        """ Steps:
+
+            1. Save wifi hotspot configuration with open network 2G band
+               (Not allowed manually. b/72412729)
+            2. Turn on wifi hotspot
+            3. Connect tethered device and verify internet connectivity
+            4. Turn off wifi hotspot
+            5. Repeat steps 2 to 4
+        """
+        # save open network wifi ap configuration with 2G band
+        config = {wutils.WifiEnums.SSID_KEY:
+                  self.open_network[wutils.WifiEnums.SSID_KEY]}
+        config[wutils.WifiEnums.AP_BAND_KEY] = WIFI_CONFIG_APBAND_2G
+        self._save_wifi_softap_configuration(self.hotspot_device, config)
+
+        # turn on/off wifi hotspot, connect device
+        for _ in range(9):
+            self._turn_on_wifi_hotspot(self.hotspot_device)
+            wutils.connect_to_wifi_network(self.tethered_devices[0], self.open_network)
+            wutils.stop_wifi_tethering(self.hotspot_device)
+            time.sleep(1) # wait for some time before turning on hotspot
+
+    @test_tracker_info(uuid="c7ef840c-4003-41fc-80e3-755f9057b542")
+    def test_wifi_tethering_open_network_5g_stress(self):
+        """ Steps:
+
+            1. Save wifi hotspot configuration with open network 5G band
+               (Not allowed manually. b/72412729)
+            2. Turn on wifi hotspot
+            3. Connect tethered device and verify internet connectivity
+            4. Turn off wifi hotspot
+            5. Repeat steps 2 to 4
+        """
+        # save open network wifi ap configuration with 5G band
+        config = {wutils.WifiEnums.SSID_KEY:
+                  self.open_network[wutils.WifiEnums.SSID_KEY]}
+        config[wutils.WifiEnums.AP_BAND_KEY] = WIFI_CONFIG_APBAND_5G
+        self._save_wifi_softap_configuration(self.hotspot_device, config)
+
+        # turn on/off wifi hotspot, connect device
+        for _ in range(9):
+            self._turn_on_wifi_hotspot(self.hotspot_device)
+            wutils.connect_to_wifi_network(self.tethered_devices[0], self.open_network)
+            wutils.stop_wifi_tethering(self.hotspot_device)
+            time.sleep(1) # wait for some time before turning on hotspot
diff --git a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
new file mode 100644
index 0000000..71ed011
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
@@ -0,0 +1,627 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import itertools
+import json
+import logging
+import numpy
+import os
+import time
+from acts import asserts
+from acts import base_test
+from acts import context
+from acts import utils
+from acts.controllers import iperf_server as ipf
+from acts.controllers.utils_lib import ssh
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from functools import partial
+
+TEST_TIMEOUT = 10
+SHORT_SLEEP = 1
+MED_SLEEP = 6
+
+
+class WifiThroughputStabilityTest(base_test.BaseTestClass):
+    """Class to test WiFi throughput stability.
+
+    This class tests throughput stability and identifies cases where throughput
+    fluctuates over time. The class setups up the AP, configures and connects
+    the phone, and runs iperf throughput test at several attenuations For an
+    example config file to run this test class see
+    example_connectivity_performance_ap_sta.json.
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        # Define metrics to be uploaded to BlackBox
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+        # Generate test cases
+        self.tests = self.generate_test_cases([6, 36, 149],
+                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['TCP', 'UDP'], ['DL', 'UL'],
+                                              ['high', 'low'])
+
+    def generate_test_cases(self, channels, modes, traffic_types,
+                            traffic_directions, signal_levels):
+        """Function that auto-generates test cases for a test class."""
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+        test_cases = []
+        for channel, mode, signal_level, traffic_type, traffic_direction in itertools.product(
+                channels,
+                modes,
+                signal_levels,
+                traffic_types,
+                traffic_directions,
+        ):
+            if channel not in allowed_configs[mode]:
+                continue
+            testcase_params = collections.OrderedDict(
+                channel=channel,
+                mode=mode,
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction,
+                signal_level=signal_level)
+            testcase_name = ('test_tput_stability'
+                             '_{}_{}_{}_ch{}_{}'.format(
+                                 signal_level, traffic_type, traffic_direction,
+                                 channel, mode))
+            setattr(self, testcase_name,
+                    partial(self._test_throughput_stability, testcase_params))
+            test_cases.append(testcase_name)
+        return test_cases
+
+    def setup_class(self):
+        self.dut = self.android_devices[0]
+        req_params = [
+            'throughput_stability_test_params', 'testbed_params',
+            'main_network', 'RetailAccessPoints', 'RemoteServer'
+        ]
+        self.unpack_userparams(req_params)
+        self.testclass_params = self.throughput_stability_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.remote_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
+        self.iperf_server = self.iperf_servers[0]
+        self.iperf_client = self.iperf_clients[0]
+        self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+        self.log_path = os.path.join(logging.log_path, 'test_results')
+        os.makedirs(self.log_path, exist_ok=True)
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+        self.ref_attenuations = {}
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+                                "Can not turn on airplane mode.")
+        wutils.wifi_toggle_state(self.dut, True)
+
+    def teardown_test(self):
+        self.iperf_server.stop()
+
+    def pass_fail_check(self, test_result_dict):
+        """Check the test result and decide if it passed or failed.
+
+        Checks the throughput stability test's PASS/FAIL criteria based on
+        minimum instantaneous throughput, and standard deviation.
+
+        Args:
+            test_result_dict: dict containing attenuation, throughput and other
+            meta data
+        """
+        avg_throughput = test_result_dict['iperf_results']['avg_throughput']
+        min_throughput = test_result_dict['iperf_results']['min_throughput']
+        std_dev_percent = (
+            test_result_dict['iperf_results']['std_deviation'] /
+            test_result_dict['iperf_results']['avg_throughput']) * 100
+        # Set blackbox metrics
+        if self.publish_testcase_metrics:
+            self.testcase_metric_logger.add_metric('avg_throughput',
+                                                   avg_throughput)
+            self.testcase_metric_logger.add_metric('min_throughput',
+                                                   min_throughput)
+            self.testcase_metric_logger.add_metric('std_dev_percent',
+                                                   std_dev_percent)
+        # Evaluate pass/fail
+        min_throughput_check = (
+            (min_throughput / avg_throughput) *
+            100) > self.testclass_params['min_throughput_threshold']
+        std_deviation_check = std_dev_percent < self.testclass_params[
+            'std_deviation_threshold']
+
+        test_message = (
+            'Atten: {0:.2f}dB, RSSI: {1:.2f}dB. '
+            'Throughput (Mean: {2:.2f}, Std. Dev:{3:.2f}%, Min: {4:.2f} Mbps).'
+            'LLStats : {5}'.format(test_result_dict['attenuation'],
+                                   test_result_dict['rssi'], avg_throughput,
+                                   std_dev_percent, min_throughput,
+                                   test_result_dict['llstats']))
+        if min_throughput_check and std_deviation_check:
+            asserts.explicit_pass('Test Passed.' + test_message)
+        asserts.fail('Test Failed. ' + test_message)
+
+    def post_process_results(self, test_result):
+        """Extracts results and saves plots and JSON formatted results.
+
+        Args:
+            test_result: dict containing attenuation, iPerfResult object and
+            other meta data
+        Returns:
+            test_result_dict: dict containing post-processed results including
+            avg throughput, other metrics, and other meta data
+        """
+        # Save output as text file
+        test_name = self.current_test_name
+        results_file_path = os.path.join(self.log_path,
+                                         '{}.txt'.format(test_name))
+        test_result_dict = {}
+        test_result_dict['ap_settings'] = test_result['ap_settings'].copy()
+        test_result_dict['attenuation'] = test_result['attenuation']
+        test_result_dict['rssi'] = test_result['rssi_result'][
+            'signal_poll_rssi']['mean']
+        test_result_dict['llstats'] = (
+            'TX MCS = {0} ({1:.1f}%). '
+            'RX MCS = {2} ({3:.1f}%)'.format(
+                test_result['llstats']['summary']['common_tx_mcs'],
+                test_result['llstats']['summary']['common_tx_mcs_freq'] * 100,
+                test_result['llstats']['summary']['common_rx_mcs'],
+                test_result['llstats']['summary']['common_rx_mcs_freq'] * 100))
+        if test_result['iperf_result'].instantaneous_rates:
+            instantaneous_rates_Mbps = [
+                rate * 8 * (1.024**2)
+                for rate in test_result['iperf_result'].instantaneous_rates[
+                    self.testclass_params['iperf_ignored_interval']:-1]
+            ]
+            tput_standard_deviation = test_result[
+                'iperf_result'].get_std_deviation(
+                    self.testclass_params['iperf_ignored_interval']) * 8
+        else:
+            instantaneous_rates_Mbps = float('nan')
+            tput_standard_deviation = float('nan')
+        test_result_dict['iperf_results'] = {
+            'instantaneous_rates': instantaneous_rates_Mbps,
+            'avg_throughput': numpy.mean(instantaneous_rates_Mbps),
+            'std_deviation': tput_standard_deviation,
+            'min_throughput': min(instantaneous_rates_Mbps)
+        }
+        with open(results_file_path, 'w') as results_file:
+            json.dump(test_result_dict, results_file)
+        # Plot and save
+        figure = wputils.BokehFigure(test_name,
+                                     x_label='Time (s)',
+                                     primary_y_label='Throughput (Mbps)')
+        time_data = list(range(0, len(instantaneous_rates_Mbps)))
+        figure.add_line(time_data,
+                        instantaneous_rates_Mbps,
+                        legend=self.current_test_name,
+                        marker='circle')
+        output_file_path = os.path.join(self.log_path,
+                                        '{}.html'.format(test_name))
+        figure.generate_figure(output_file_path)
+        return test_result_dict
+
+    def setup_ap(self, testcase_params):
+        """Sets up the access point in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if '2G' in band:
+            frequency = wutils.WifiEnums.channel_2G_to_freq[
+                testcase_params['channel']]
+        else:
+            frequency = wutils.WifiEnums.channel_5G_to_freq[
+                testcase_params['channel']]
+        if frequency in wutils.WifiEnums.DFS_5G_FREQUENCIES:
+            self.access_point.set_region(self.testbed_params['DFS_region'])
+        else:
+            self.access_point.set_region(self.testbed_params['default_region'])
+        self.access_point.set_channel(band, testcase_params['channel'])
+        self.access_point.set_bandwidth(band, testcase_params['mode'])
+        self.log.info('Access Point Configuration: {}'.format(
+            self.access_point.ap_settings))
+
+    def setup_dut(self, testcase_params):
+        """Sets up the DUT in the configuration required by the test.
+
+        Args:
+            testcase_params: dict containing AP and other test params
+        """
+        # Check battery level before test
+        if not wputils.health_check(self.dut, 10):
+            asserts.skip('Battery level too low. Skipping test.')
+        # Turn screen off to preserve battery
+        self.dut.go_to_sleep()
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        if wputils.validate_network(self.dut,
+                                    testcase_params['test_network']['SSID']):
+            self.log.info('Already connected to desired network')
+        else:
+            wutils.wifi_toggle_state(self.dut, True)
+            wutils.reset_wifi(self.dut)
+            wutils.set_wifi_country_code(self.dut,
+                                         self.testclass_params['country_code'])
+            self.main_network[band]['channel'] = testcase_params['channel']
+            wutils.wifi_connect(self.dut,
+                                testcase_params['test_network'],
+                                num_of_tries=5,
+                                check_connectivity=False)
+        self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+    def setup_throughput_stability_test(self, testcase_params):
+        """Function that gets devices ready for the test.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Configure AP
+        self.setup_ap(testcase_params)
+        # Reset, configure, and connect DUT
+        self.setup_dut(testcase_params)
+        # Wait before running the first wifi test
+        first_test_delay = self.testclass_params.get('first_test_delay', 600)
+        if first_test_delay > 0 and len(self.testclass_results) == 0:
+            self.log.info('Waiting before the first test.')
+            time.sleep(first_test_delay)
+            self.setup_dut(testcase_params)
+        # Get and set attenuation levels for test
+        testcase_params['atten_level'] = self.get_target_atten(testcase_params)
+        self.log.info('Setting attenuation to {} dB'.format(
+            testcase_params['atten_level']))
+        for attenuator in self.attenuators:
+            attenuator.set_atten(testcase_params['atten_level'])
+        # Configure iperf
+        if isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+            testcase_params['iperf_server_address'] = self.dut_ip
+        else:
+            testcase_params[
+                'iperf_server_address'] = wputils.get_server_address(
+                    self.remote_server, self.dut_ip, '255.255.255.0')
+
+    def run_throughput_stability_test(self, testcase_params):
+        """Main function to test throughput stability.
+
+        The function sets up the AP in the correct channel and mode
+        configuration and runs an iperf test to measure throughput.
+
+        Args:
+            testcase_params: dict containing test specific parameters
+        Returns:
+            test_result: dict containing test result and meta data
+        """
+        # Run test and log result
+        # Start iperf session
+        self.log.info('Starting iperf test.')
+        llstats_obj = wputils.LinkLayerStats(self.dut)
+        llstats_obj.update_stats()
+        self.iperf_server.start(tag=str(testcase_params['atten_level']))
+        current_rssi = wputils.get_connected_rssi_nb(
+            dut=self.dut,
+            num_measurements=self.testclass_params['iperf_duration'] - 1,
+            polling_frequency=1,
+            first_measurement_delay=1,
+            disconnect_warning=1,
+            ignore_samples=1)
+        client_output_path = self.iperf_client.start(
+            testcase_params['iperf_server_address'],
+            testcase_params['iperf_args'], str(testcase_params['atten_level']),
+            self.testclass_params['iperf_duration'] + TEST_TIMEOUT)
+        current_rssi = current_rssi.result()
+        server_output_path = self.iperf_server.stop()
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0)
+        # Parse and log result
+        if testcase_params['use_client_output']:
+            iperf_file = client_output_path
+        else:
+            iperf_file = server_output_path
+        try:
+            iperf_result = ipf.IPerfResult(iperf_file)
+        except:
+            asserts.fail('Cannot get iperf result.')
+        llstats_obj.update_stats()
+        curr_llstats = llstats_obj.llstats_incremental.copy()
+        test_result = collections.OrderedDict()
+        test_result['testcase_params'] = testcase_params.copy()
+        test_result['ap_settings'] = self.access_point.ap_settings.copy()
+        test_result['attenuation'] = testcase_params['atten_level']
+        test_result['iperf_result'] = iperf_result
+        test_result['rssi_result'] = current_rssi
+        test_result['llstats'] = curr_llstats
+        self.testclass_results.append(test_result)
+        return test_result
+
+    def get_target_atten(self, testcase_params):
+        """Function gets attenuation used for test
+
+        The function fetches the attenuation at which the test should be
+        performed.
+
+        Args:
+            testcase_params: dict containing test specific parameters
+        Returns:
+            test_atten: target attenuation for test
+        """
+        # Get attenuation from reference test if it has been run
+        ref_test_fields = ['channel', 'mode', 'signal_level']
+        test_id = wputils.extract_sub_dict(testcase_params, ref_test_fields)
+        test_id = tuple(test_id.items())
+        if test_id in self.ref_attenuations:
+            return self.ref_attenuations[test_id]
+
+        # Get attenuation for target RSSI
+        if testcase_params['signal_level'] == 'low':
+            target_rssi = self.testclass_params['low_throughput_target']
+        else:
+            target_rssi = self.testclass_params['high_throughput_target']
+        target_atten = wputils.get_atten_for_target_rssi(
+            target_rssi, self.attenuators, self.dut, self.remote_server)
+
+        self.ref_attenuations[test_id] = target_atten
+        return self.ref_attenuations[test_id]
+
+    def compile_test_params(self, testcase_params):
+        """Function that completes setting the test case parameters."""
+        band = self.access_point.band_lookup_by_channel(
+            testcase_params['channel'])
+        testcase_params['test_network'] = self.main_network[band]
+
+        if testcase_params['traffic_type'] == 'TCP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'tcp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'tcp_processes', 1)
+        elif testcase_params['traffic_type'] == 'UDP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'udp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'udp_processes', 1)
+        if (testcase_params['traffic_direction'] == 'DL'
+                and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
+            ) or (testcase_params['traffic_direction'] == 'UL'
+                  and isinstance(self.iperf_server, ipf.IPerfServerOverAdb)):
+            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+                duration=self.testclass_params['iperf_duration'],
+                reverse_direction=1,
+                traffic_type=testcase_params['traffic_type'],
+                socket_size=testcase_params['iperf_socket_size'],
+                num_processes=testcase_params['iperf_processes'])
+            testcase_params['use_client_output'] = True
+        else:
+            testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+                duration=self.testclass_params['iperf_duration'],
+                reverse_direction=0,
+                traffic_type=testcase_params['traffic_type'],
+                socket_size=testcase_params['iperf_socket_size'],
+                num_processes=testcase_params['iperf_processes'])
+            testcase_params['use_client_output'] = False
+
+        return testcase_params
+
+    def _test_throughput_stability(self, testcase_params):
+        """ Function that gets called for each test case
+
+        The function gets called in each test case. The function customizes
+        the test based on the test name of the test that called it
+
+        Args:
+            testcase_params: dict containing test specific parameters
+        """
+        testcase_params = self.compile_test_params(testcase_params)
+        self.setup_throughput_stability_test(testcase_params)
+        test_result = self.run_throughput_stability_test(testcase_params)
+        test_result_postprocessed = self.post_process_results(test_result)
+        self.pass_fail_check(test_result_postprocessed)
+
+
+# Over-the air version of ping tests
+class WifiOtaThroughputStabilityTest(WifiThroughputStabilityTest):
+    """Class to test over-the-air ping
+
+    This class tests WiFi ping performance in an OTA chamber. It enables
+    setting turntable orientation and other chamber parameters to study
+    performance in varying channel conditions
+    """
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        # Define metrics to be uploaded to BlackBox
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = False
+
+    def setup_class(self):
+        WifiThroughputStabilityTest.setup_class(self)
+        self.ota_chamber = ota_chamber.create(
+            self.user_params['OTAChamber'])[0]
+
+    def teardown_class(self):
+        self.ota_chamber.reset_chamber()
+        self.process_testclass_results()
+
+    def extract_test_id(self, testcase_params, id_fields):
+        test_id = collections.OrderedDict(
+            (param, testcase_params[param]) for param in id_fields)
+        return test_id
+
+    def process_testclass_results(self):
+        """Saves all test results to enable comparison."""
+        testclass_data = collections.OrderedDict()
+        for test in self.testclass_results:
+            current_params = test['testcase_params']
+            channel_data = testclass_data.setdefault(current_params['channel'],
+                                                     collections.OrderedDict())
+            test_id = tuple(
+                self.extract_test_id(current_params, [
+                    'mode', 'traffic_type', 'traffic_direction', 'signal_level'
+                ]).items())
+            test_data = channel_data.setdefault(
+                test_id, collections.OrderedDict(position=[], throughput=[]))
+            current_throughput = (numpy.mean(
+                test['iperf_result'].instantaneous_rates[
+                    self.testclass_params['iperf_ignored_interval']:-1])
+                                  ) * 8 * (1.024**2)
+            test_data['position'].append(current_params['position'])
+            test_data['throughput'].append(current_throughput)
+
+        chamber_mode = self.testclass_results[0]['testcase_params'][
+            'chamber_mode']
+        if chamber_mode == 'orientation':
+            x_label = 'Angle (deg)'
+        elif chamber_mode == 'stepped stirrers':
+            x_label = 'Position Index'
+
+        # Publish test class metrics
+        for channel, channel_data in testclass_data.items():
+            for test_id, test_data in channel_data.items():
+                test_id_dict = dict(test_id)
+                metric_tag = 'ota_summary_{}_{}_{}_ch{}_{}'.format(
+                    test_id_dict['signal_level'], test_id_dict['traffic_type'],
+                    test_id_dict['traffic_direction'], channel,
+                    test_id_dict['mode'])
+                metric_name = metric_tag + '.avg_throughput'
+                metric_value = numpy.mean(test_data['throughput'])
+                self.testclass_metric_logger.add_metric(
+                    metric_name, metric_value)
+                metric_name = metric_tag + '.min_throughput'
+                metric_value = min(test_data['throughput'])
+                self.testclass_metric_logger.add_metric(
+                    metric_name, metric_value)
+
+        # Plot test class results
+        plots = []
+        for channel, channel_data in testclass_data.items():
+            current_plot = wputils.BokehFigure(
+                title='Channel {} - Rate vs. Position'.format(channel),
+                x_label=x_label,
+                primary_y_label='Rate (Mbps)',
+            )
+            for test_id, test_data in channel_data.items():
+                test_id_dict = dict(test_id)
+                legend = '{}, {} {}, {} RSSI'.format(
+                    test_id_dict['mode'], test_id_dict['traffic_type'],
+                    test_id_dict['traffic_direction'],
+                    test_id_dict['signal_level'])
+                current_plot.add_line(test_data['position'],
+                                      test_data['throughput'], legend)
+            current_plot.generate_figure()
+            plots.append(current_plot)
+        current_context = context.get_current_context().get_full_output_path()
+        plot_file_path = os.path.join(current_context, 'results.html')
+        wputils.BokehFigure.save_figures(plots, plot_file_path)
+
+    def setup_throughput_stability_test(self, testcase_params):
+        WifiThroughputStabilityTest.setup_throughput_stability_test(
+            self, testcase_params)
+        # Setup turntable
+        if testcase_params['chamber_mode'] == 'orientation':
+            self.ota_chamber.set_orientation(testcase_params['position'])
+        elif testcase_params['chamber_mode'] == 'stepped stirrers':
+            self.ota_chamber.step_stirrers(testcase_params['total_positions'])
+
+    def get_target_atten(self, testcase_params):
+        if testcase_params['signal_level'] == 'high':
+            test_atten = self.testclass_params['default_atten_levels'][0]
+        elif testcase_params['signal_level'] == 'low':
+            test_atten = self.testclass_params['default_atten_levels'][1]
+        return test_atten
+
+    def generate_test_cases(self, channels, modes, traffic_types,
+                            traffic_directions, signal_levels, chamber_mode,
+                            positions):
+        allowed_configs = {
+            'VHT20': [
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48, 149, 153,
+                157, 161
+            ],
+            'VHT40': [36, 44, 149, 157],
+            'VHT80': [36, 149]
+        }
+        test_cases = []
+        for channel, mode, position, traffic_type, signal_level, traffic_direction in itertools.product(
+                channels, modes, positions, traffic_types, signal_levels,
+                traffic_directions):
+            if channel not in allowed_configs[mode]:
+                continue
+            testcase_params = collections.OrderedDict(
+                channel=channel,
+                mode=mode,
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction,
+                signal_level=signal_level,
+                chamber_mode=chamber_mode,
+                total_positions=len(positions),
+                position=position)
+            testcase_name = ('test_tput_stability'
+                             '_{}_{}_{}_ch{}_{}_pos{}'.format(
+                                 signal_level, traffic_type, traffic_direction,
+                                 channel, mode, position))
+            setattr(self, testcase_name,
+                    partial(self._test_throughput_stability, testcase_params))
+            test_cases.append(testcase_name)
+        return test_cases
+
+
+class WifiOtaThroughputStability_TenDegree_Test(WifiOtaThroughputStabilityTest
+                                                ):
+    def __init__(self, controllers):
+        WifiOtaThroughputStabilityTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases([6, 36, 149], ['VHT20', 'VHT80'],
+                                              ['TCP'], ['DL', 'UL'],
+                                              ['high', 'low'], 'orientation',
+                                              list(range(0, 360, 10)))
+
+
+class WifiOtaThroughputStability_45Degree_Test(WifiOtaThroughputStabilityTest):
+    def __init__(self, controllers):
+        WifiOtaThroughputStabilityTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases([6, 36, 149], ['VHT20', 'VHT80'],
+                                              ['TCP'], ['DL', 'UL'],
+                                              ['high', 'low'], 'orientation',
+                                              list(range(0, 360, 45)))
+
+
+class WifiOtaThroughputStability_SteppedStirrers_Test(
+        WifiOtaThroughputStabilityTest):
+    def __init__(self, controllers):
+        WifiOtaThroughputStabilityTest.__init__(self, controllers)
+        self.tests = self.generate_test_cases([6, 36, 149], ['VHT20', 'VHT80'],
+                                              ['TCP'], ['DL', 'UL'],
+                                              ['high', 'low'],
+                                              'stepped stirrers',
+                                              list(range(100)))
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiWakeTest.py b/acts_tests/tests/google/wifi/WifiWakeTest.py
new file mode 100644
index 0000000..52ab2fd
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiWakeTest.py
@@ -0,0 +1,421 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import queue
+
+from acts import asserts
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+
+WifiEnums = wutils.WifiEnums
+SSID = WifiEnums.SSID_KEY
+CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT = 5
+SCANS_REQUIRED_TO_FIND_SSID = 5
+LAST_DISCONNECT_TIMEOUT_MILLIS = 5000
+LAST_DISCONNECT_TIMEOUT_SEC = LAST_DISCONNECT_TIMEOUT_MILLIS / 1000
+PRESCAN_DELAY_SEC = 5
+WIFI_TOGGLE_DELAY_SEC = 3
+DISCONNECT_TIMEOUT_SEC = 20
+
+
+class WifiWakeTest(WifiBaseTest):
+    """
+    Tests Wifi Wake.
+
+    Test Bed Requirements:
+    * One Android Device
+    * Two APs that can be turned on and off
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        # turn location back on
+        acts.utils.set_location_service(self.dut, True)
+        self.dut.droid.wifiScannerToggleAlwaysAvailable(True)
+
+        self.unpack_userparams(req_param_names=[],
+                               opt_param_names=["reference_networks"])
+
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(mirror_ap=False, ap_count=2)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                ap_count=2)
+
+        # use 2G since Wifi Wake does not work if an AP is on a 5G DFS channel
+        self.ap_a = self.reference_networks[0]["2g"]
+        self.ap_b = self.reference_networks[1]["2g"]
+
+        self.ap_a_atten = self.attenuators[0]
+        self.ap_b_atten = self.attenuators[2]
+        if "OpenWrtAP" in self.user_params:
+            self.ap_b_atten = self.attenuators[1]
+
+    # TODO(b/119040540): this method of disabling/re-enabling Wifi on APs is
+    # hacky, switch to using public methods when they are implemented
+    def ap_a_off(self):
+        if "OpenWrtAP" in self.user_params:
+            self.access_points[0].stop_ap()
+            self.log.info('Turned AP A off')
+            return
+        ap_a_hostapd = self.access_points[0]._aps['wlan0'].hostapd
+        if ap_a_hostapd.is_alive():
+            ap_a_hostapd.stop()
+            self.log.info('Turned AP A off')
+
+    def ap_a_on(self):
+        if "OpenWrtAP" in self.user_params:
+            self.access_points[0].start_ap()
+            self.log.info('Turned AP A on')
+            return
+        ap_a_hostapd = self.access_points[0]._aps['wlan0'].hostapd
+        if not ap_a_hostapd.is_alive():
+            ap_a_hostapd.start(ap_a_hostapd.config)
+            self.log.info('Turned AP A on')
+
+    def ap_b_off(self):
+        if "OpenWrtAP" in self.user_params:
+            self.access_points[1].stop_ap()
+            self.log.info('Turned AP B off')
+            return
+        ap_b_hostapd = self.access_points[1]._aps['wlan0'].hostapd
+        if ap_b_hostapd.is_alive():
+            ap_b_hostapd.stop()
+            self.log.info('Turned AP B off')
+
+    def ap_b_on(self):
+        if "OpenWrtAP" in self.user_params:
+            self.access_points[1].start_ap()
+            self.log.info('Turned AP B on')
+            return
+        ap_b_hostapd = self.access_points[1]._aps['wlan0'].hostapd
+        if not ap_b_hostapd.is_alive():
+            ap_b_hostapd.start(ap_b_hostapd.config)
+            self.log.info('Turned AP B on')
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut.droid.wakeLockAcquireBright()
+        self.dut.droid.wakeUpNow()
+        self.ap_a_on()
+        self.ap_b_on()
+        self.ap_a_atten.set_atten(0)
+        self.ap_b_atten.set_atten(0)
+        wutils.reset_wifi(self.dut)
+        wutils.wifi_toggle_state(self.dut, new_state=True)
+        # clear events from event dispatcher
+        self.dut.droid.wifiStartTrackingStateChange()
+        self.dut.droid.wifiStopTrackingStateChange()
+        self.dut.ed.clear_all_events()
+
+    def teardown_test(self):
+        super().teardown_test()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+
+    def find_ssid_in_scan_results(self, scan_results_batches, ssid):
+        scan_results_batch = scan_results_batches[0]
+        scan_results = scan_results_batch["ScanResults"]
+        for scan_result in scan_results:
+            if ssid == scan_result["SSID"]:
+               return True
+        return False
+
+    def do_location_scan(self, num_times=1, ssid_to_find=None):
+        scan_settings = {
+            "band": wutils.WifiEnums.WIFI_BAND_BOTH,
+            "periodInMs": 0,
+            "reportEvents": wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN
+        }
+
+        wifi_chs = wutils.WifiChannelUS(self.dut.model)
+        stime_channel = 47  # dwell time plus 2ms
+        leeway = 10
+
+        for i in range(num_times):
+            self.log.info("Scan count: {}".format(i))
+            data = wutils.start_wifi_single_scan(self.dut, scan_settings)
+            idx = data["Index"]
+            scan_rt = data["ScanElapsedRealtime"]
+            self.log.debug(
+                "Wifi single shot scan started index: %s at real time: %s", idx,
+                scan_rt)
+            # generating event wait time from scan setting plus leeway
+            scan_time, scan_channels = wutils.get_scan_time_and_channels(
+                wifi_chs, scan_settings, stime_channel)
+            wait_time = int(scan_time / 1000) + leeway
+            # track number of result received
+            result_received = 0
+            try:
+                for _ in range(1, 3):
+                    event_name = "{}{}onResults".format("WifiScannerScan", idx)
+                    self.log.debug("Waiting for event: %s for time %s",
+                                   event_name, wait_time)
+                    event = self.dut.ed.pop_event(event_name, wait_time)
+                    self.log.debug("Event received: %s", event)
+                    result_received += 1
+                    scan_results_batches = event["data"]["Results"]
+                    if ssid_to_find and self.find_ssid_in_scan_results(
+                        scan_results_batches, ssid_to_find):
+                        return
+            except queue.Empty as error:
+                asserts.assert_true(
+                    result_received >= 1,
+                    "Event did not triggered for single shot {}".format(error))
+            finally:
+                self.dut.droid.wifiScannerStopScan(idx)
+                # For single shot number of result received and length of result
+                # should be one
+                asserts.assert_true(
+                    result_received == 1,
+                    "Test fail because received result {}".format(
+                        result_received))
+
+    @test_tracker_info(uuid="372b9b74-4241-46ce-8f18-e6a97d3a3452")
+    def test_no_reconnect_manual_disable_wifi(self):
+        """
+        Tests that Wifi Wake does not reconnect to a network if the user turned
+        off Wifi while connected to that network and the user has not moved
+        (i.e. moved out of range of the AP then came back).
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(
+            2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        asserts.assert_false(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to not enable Wifi, but Wifi was enabled.")
+
+    @test_tracker_info(uuid="ec7a54a5-f293-43f5-a1dd-d41679aa1825")
+    def test_reconnect_wifi_saved_network(self):
+        """Tests that Wifi Wake re-enables Wifi for a saved network."""
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
+        self.dut.ed.clear_all_events()
+        self.ap_a_off()
+        self.ap_b_off()
+        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
+        self.log.info("Wifi Disconnected")
+        self.do_location_scan(2)
+        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+
+        self.ap_a_on()
+        self.do_location_scan(
+            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
+        time.sleep(WIFI_TOGGLE_DELAY_SEC)
+        asserts.assert_true(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
+
+    @test_tracker_info(uuid="3cecd1c5-54bc-44a2-86f7-ad84625bf094")
+    def test_reconnect_wifi_network_suggestion(self):
+        """Tests that Wifi Wake re-enables Wifi for app provided suggestion."""
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([self.ap_a]),
+            "Failed to add suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([self.ap_b]),
+            "Failed to add suggestions")
+        # Enable suggestions by the app.
+        self.dut.log.debug("Enabling suggestions from test")
+        self.dut.adb.shell("cmd wifi network-suggestions-set-user-approved"
+                           + " " + SL4A_APK_NAME + " yes")
+        # Ensure network is seen in scan results & auto-connected to.
+        self.do_location_scan(2)
+        wutils.wait_for_connect(self.dut)
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.dut.ed.clear_all_events()
+        if current_network[SSID] == self.ap_a[SSID]:
+            # connected to AP A, so turn AP B off first to prevent the
+            # device from immediately reconnecting to AP B
+            self.ap_b_off()
+            self.ap_a_off()
+        else:
+            self.ap_a_off()
+            self.ap_b_off()
+
+        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
+        self.log.info("Wifi Disconnected")
+        self.do_location_scan(2)
+        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+
+        self.ap_a_on()
+        self.do_location_scan(
+            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
+        time.sleep(WIFI_TOGGLE_DELAY_SEC)
+        asserts.assert_true(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
+
+    @test_tracker_info(uuid="6c77ca9b-ff34-4bc7-895f-cc7340e0e645")
+    def test_reconnect_wifi_move_back_in_range(self):
+        """
+        Tests that Wifi Wake re-enables Wifi if the device moves out of range of
+        the AP then came back.
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        # init Wakeup Lock with AP A
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_a_off()
+        # evict AP A from Wakeup Lock
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_a_on()
+        self.do_location_scan(
+            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
+        time.sleep(WIFI_TOGGLE_DELAY_SEC)
+        asserts.assert_true(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
+
+    @test_tracker_info(uuid="08e8284a-a523-48f3-b9ea-9c6bf27d711e")
+    def test_no_reconnect_to_flaky_ap(self):
+        """
+        Tests that Wifi Wake does not reconnect to flaky networks.
+        If a network sporadically connects and disconnects, and the user turns
+        off Wifi even during the disconnected phase, Wifi Wake should not
+        re-enable Wifi for that network.
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        self.ap_a_off()
+        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 0.4)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_a_on()
+        self.do_location_scan(
+            2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        asserts.assert_false(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to not enable Wifi, but Wifi was enabled.")
+
+    @test_tracker_info(uuid="b990a8f7-e3a0-4774-89cf-2067ccd64903")
+    def test_reconnect_wifi_disabled_after_disconnecting(self):
+        """
+        Tests that Wifi Wake reconnects to a network if Wifi was disabled long
+        after disconnecting from a network.
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        self.dut.ed.clear_all_events()
+        self.ap_a_off()
+        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
+        self.log.info("Wifi Disconnected")
+        self.do_location_scan(2)
+        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_a_on()
+        self.do_location_scan(
+            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
+        time.sleep(WIFI_TOGGLE_DELAY_SEC)
+        asserts.assert_true(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
+
+    @test_tracker_info(uuid="bb217794-d3ee-4fb9-87ff-7a594d0223b0")
+    def test_no_reconnect_if_exists_ap_in_wakeup_lock(self):
+        """
+        2 APs in Wakeup Lock, user moves out of range of one AP but stays in
+        range of the other, should not reconnect when user moves back in range
+        of both.
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_b_off()
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_b_on()
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        asserts.assert_false(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to not enable Wifi, but Wifi was enabled.")
+
+    @test_tracker_info(uuid="567a0663-4ce0-488d-8fe2-db79a3ebf068")
+    def test_reconnect_if_both_ap_evicted_from_wakeup_lock(self):
+        """
+        2 APs in Wakeup Lock, user moves out of range of both APs, should
+        reconnect when user moves back in range of either AP.
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_a_off()
+        self.ap_b_off()
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+        self.ap_a_on()
+        self.do_location_scan(
+            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
+        time.sleep(WIFI_TOGGLE_DELAY_SEC)
+        asserts.assert_true(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
+
+    @test_tracker_info(uuid="d67657c8-3de3-46a6-a103-428cdab89423")
+    def test_reconnect_to_better_saved_network(self):
+        """
+        2 saved APs, one attenuated, one unattenuated, Wifi Wake should connect
+        to the unattenuated AP
+        """
+        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
+        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
+        self.dut.ed.clear_all_events()
+        self.ap_a_off()
+        self.ap_b_off()
+        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
+        self.log.info("Wifi Disconnected")
+        self.do_location_scan(2)
+        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
+        wutils.wifi_toggle_state(self.dut, new_state=False)
+        time.sleep(PRESCAN_DELAY_SEC)
+        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
+
+        self.ap_a_on()
+        self.ap_b_on()
+        self.ap_a_atten.set_atten(30)
+        self.ap_b_atten.set_atten(0)
+
+        self.do_location_scan(
+            SCANS_REQUIRED_TO_FIND_SSID, self.ap_b[wutils.WifiEnums.SSID_KEY])
+        time.sleep(WIFI_TOGGLE_DELAY_SEC)
+        asserts.assert_true(
+            self.dut.droid.wifiCheckState(),
+            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
+        expected_ssid = self.ap_b[wutils.WifiEnums.SSID_KEY]
+        wutils.wait_for_connect(self.dut, expected_ssid)
diff --git a/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py b/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
new file mode 100644
index 0000000..5257766
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+WifiEnums = wutils.WifiEnums
+
+EAP = WifiEnums.Eap
+Ent = WifiEnums.Enterprise
+WPA3_SECURITY = "SUITE_B_192"
+
+
+class WifiWpa3EnterpriseTest(WifiBaseTest):
+  """Tests for WPA3 Enterprise."""
+
+  def setup_class(self):
+    super().setup_class()
+
+    self.dut = self.android_devices[0]
+    wutils.wifi_test_device_init(self.dut)
+    req_params = [
+        "ec2_ca_cert", "ec2_client_cert", "ec2_client_key", "rsa3072_ca_cert",
+        "rsa3072_client_cert", "rsa3072_client_key", "wpa3_ec2_network",
+        "wpa3_rsa3072_network"
+    ]
+    self.unpack_userparams(req_param_names=req_params,)
+
+  def setup_test(self):
+    super().setup_test()
+    for ad in self.android_devices:
+      ad.droid.wakeLockAcquireBright()
+      ad.droid.wakeUpNow()
+    wutils.wifi_toggle_state(self.dut, True)
+
+  def teardown_test(self):
+    super().teardown_test()
+    for ad in self.android_devices:
+      ad.droid.wakeLockRelease()
+      ad.droid.goToSleepNow()
+    wutils.reset_wifi(self.dut)
+
+  ### Tests ###
+
+  @test_tracker_info(uuid="404c6165-6e23-4ec1-bc2c-9dfdd5c7dc87")
+  def test_connect_to_wpa3_enterprise_ec2(self):
+    asserts.skip_if(
+        self.dut.build_info["build_id"].startswith("R"),
+        "No SL4A support for EC certs in R builds. Skipping this testcase")
+    config = {
+        Ent.EAP: int(EAP.TLS),
+        Ent.CA_CERT: self.ec2_ca_cert,
+        WifiEnums.SSID_KEY: self.wpa3_ec2_network[WifiEnums.SSID_KEY],
+        Ent.CLIENT_CERT: self.ec2_client_cert,
+        Ent.PRIVATE_KEY_ID: self.ec2_client_key,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_ec2_network["identity"],
+        "domain_suffix_match": self.wpa3_ec2_network["domain"],
+        "cert_algo": self.wpa3_ec2_network["cert_algo"]
+    }
+    wutils.connect_to_wifi_network(self.dut, config)
+
+  @test_tracker_info(uuid="b6d22585-f7c1-418d-bd4b-b627af8c228c")
+  def test_connect_to_wpa3_enterprise_rsa3072(self):
+    config = {
+        Ent.EAP: int(EAP.TLS),
+        Ent.CA_CERT: self.rsa3072_ca_cert,
+        WifiEnums.SSID_KEY: self.wpa3_rsa3072_network[WifiEnums.SSID_KEY],
+        Ent.CLIENT_CERT: self.rsa3072_client_cert,
+        Ent.PRIVATE_KEY_ID: self.rsa3072_client_key,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_rsa3072_network["identity"],
+        "domain_suffix_match": self.wpa3_rsa3072_network["domain"]
+    }
+    wutils.connect_to_wifi_network(self.dut, config)
diff --git a/acts_tests/tests/google/wifi/WifiWpa3OweTest.py b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
new file mode 100644
index 0000000..a3c70f3
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.test_utils.wifi.wifi_test_utils as wutils
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class WifiWpa3OweTest(WifiBaseTest):
+    """Tests for APIs in Android's WifiManager class.
+
+    Test Bed Requirement:
+    * At least one Android device and atleast two Access Points.
+    * Several Wi-Fi networks visible to the device.
+    """
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ["owe_networks", "sae_networks"]
+        self.unpack_userparams(req_param_names=req_params,)
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(owe_network=True,
+                                                sae_network=True)
+        self.owe_2g = self.owe_networks[0]["2g"]
+        self.owe_5g = self.owe_networks[0]["5g"]
+        self.wpa3_personal_2g = self.sae_networks[0]["2g"]
+        self.wpa3_personal_5g = self.sae_networks[0]["5g"]
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+            wutils.wifi_toggle_state(ad, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        for ad in self.android_devices:
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+        wutils.reset_wifi(self.dut)
+
+    ### Test cases ###
+
+    @test_tracker_info(uuid="a7755f1f-5740-4d45-8c29-3711172b1bd7")
+    def test_connect_to_owe_2g(self):
+        wutils.connect_to_wifi_network(self.dut, self.owe_2g)
+
+    @test_tracker_info(uuid="9977765e-03da-4614-ab96-4c1597101118")
+    def test_connect_to_owe_5g(self):
+        wutils.connect_to_wifi_network(self.dut, self.owe_5g)
+
+    @test_tracker_info(uuid="3670702a-3d78-4184-b5e1-7fcf5fa48fd8")
+    def test_connect_to_wpa3_personal_2g(self):
+        wutils.connect_to_wifi_network(self.dut, self.wpa3_personal_2g)
+
+    @test_tracker_info(uuid="c4528eaf-7960-4ecd-8f11-d5439bdf1c58")
+    def test_connect_to_wpa3_personal_5g(self):
+        wutils.connect_to_wifi_network(self.dut, self.wpa3_personal_5g)
+
+    @test_tracker_info(uuid="a8fb46be-3487-4dc8-a393-5af992b27f45")
+    def test_connect_to_wpa3_personal_reconnection(self):
+        """ This is to catch auth reject which is caused by PMKSA cache.
+            Steps:
+            ------------------------------
+            1. Connect STA to WPA3 AP
+            2. Turn off the WiFi or connect to a different AP
+            3. Turn on the WiFi or connect back to WPA3 AP.
+            4. Initial connect request fails
+               Second connect request from framework succeeds.
+        """
+        wutils.connect_to_wifi_network(self.dut, self.wpa3_personal_2g)
+        wutils.toggle_wifi_off_and_on(self.dut)
+        wutils.connect_to_wifi_network(self.dut, self.wpa3_personal_2g)
diff --git a/acts_tests/tests/google/wifi/__init__.py b/acts_tests/tests/google/wifi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/tests/google/wifi/__init__.py
diff --git a/acts_tests/tests/google/wifi/aware/README.md b/acts_tests/tests/google/wifi/aware/README.md
new file mode 100644
index 0000000..6b96914
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/README.md
@@ -0,0 +1,50 @@
+# Wi-Fi Aware Integrated (ACTS/sl4a) Test Suite
+
+This directory contains ACTS/sl4a test scripts to verify and characterize
+the Wi-Fi Aware implementation in Android.
+
+There are 4 groups of tests (in 4 sub-directories):
+
+* functional: Functional tests that each implementation must pass. These
+are pass/fail tests.
+* performance: Tests which measure performance of an implementation - e.g.
+latency or throughput. Some of the tests may not have pass/fail results -
+they just record the measured performance. Even when tests do have a pass/
+fail criteria - that criteria may not apply to all implementations.
+* stress: Tests which run through a large number of iterations to stress
+test the implementation. Considering that some failures are expected,
+especially in an over-the-air situation, pass/fail criteria are either
+not provided or may not apply to all implementations or test environments.
+* ota (over-the-air): A small number of tests which configure the device
+in a particular mode and expect the tester to capture an over-the-air
+sniffer trace and analyze it for validity. These tests are **not** automated.
+
+The tests can be executed in several ways:
+
+1. Individual test(s): `act.py -c <config> -tc {<test_class>|<test_class>:<test_name>}`
+
+Where a test file is any of the `.py` files in any of the test sub-directories.
+If a test class is specified, then all tests within that test class are executed.
+
+2. All tests in a test group: `act.py -c <config> -tf <test_file>`
+
+Where `<test_file>` is a file containing a list of tests. Each of the test
+group sub-directories contains a file with the same name as that of the
+directory which lists all tests in the directory. E.g. to execute all functional
+tests execute:
+
+`act.py -c <config> -tf ./tools/test/connectivity/acts_tests/tests/google/wifi/aware/functional/functional`
+
+## Test Configurations
+The test configurations, the `<config>` in the commands above, are stored in
+the *config* sub-directory. The configurations simply use all connected
+devices without listing specific serial numbers. Note that some tests use a
+single device while others use 2 devices. In addition, the configurations
+define the following key to configure the test:
+
+* **aware_default_power_mode**: The power mode in which to run all tests. Options
+are `INTERACTIVE` and `NON_INTERACTIVE`.
+
+The following configurations are provided:
+* wifi_aware.json: Normal (high power/interactive) test mode.
+* wifi_aware_non_interactive.json: Low power (non-interactive) test mode.
diff --git a/acts_tests/tests/google/wifi/aware/config/wifi_aware.json b/acts_tests/tests/google/wifi/aware/config/wifi_aware.json
new file mode 100644
index 0000000..8f1252e
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/config/wifi_aware.json
@@ -0,0 +1,16 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi Aware tests in INTERACTIVE (high-power) mode.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi Aware testbed: auto-detect all attached devices",
+            "name": "WifiAwareAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts_tests/tests/google/wifi"],
+    "dbs_supported_models": "*",
+    "adb_logcat_param": "-b all",
+    "aware_default_power_mode": "INTERACTIVE"
+}
diff --git a/acts_tests/tests/google/wifi/aware/config/wifi_aware_non_interactive.json b/acts_tests/tests/google/wifi/aware/config/wifi_aware_non_interactive.json
new file mode 100644
index 0000000..23dd1bb
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/config/wifi_aware_non_interactive.json
@@ -0,0 +1,16 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi Aware tests in NON-INTERACTIVE (low-power) mode.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi Aware testbed: auto-detect all attached devices",
+            "name": "WifiAwareAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts_tests/tests/google/wifi"],
+    "dbs_supported_models": "*",
+    "adb_logcat_param": "-b all",
+    "aware_default_power_mode": "NON_INTERACTIVE"
+}
diff --git a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
new file mode 100644
index 0000000..0df82a1
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
@@ -0,0 +1,161 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class AttachTest(AwareBaseTest):
+    @test_tracker_info(uuid="cdafd1e0-bcf5-4fe8-ae32-f55483db9925")
+    def test_attach(self):
+        """Functional test case / Attach test cases / attach
+
+    Validates that attaching to the Wi-Fi Aware service works (receive
+    the expected callback).
+    """
+        dut = self.android_devices[0]
+        dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        autils.fail_on_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+    @test_tracker_info(uuid="82f2a8bc-a62b-49c2-ac8a-fe8460010ba2")
+    def test_attach_with_identity(self):
+        """Functional test case / Attach test cases / attach with identity callback
+
+    Validates that attaching to the Wi-Fi Aware service works (receive
+    the expected callbacks).
+    """
+        dut = self.android_devices[0]
+        dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+    @test_tracker_info(uuid="d2714d14-f330-47d4-b8e9-ee4d5e5b7ea0")
+    def test_attach_multiple_sessions(self):
+        """Functional test case / Attach test cases / multiple attach sessions
+
+    Validates that when creating multiple attach sessions each can be
+    configured independently as to whether or not to receive an identity
+    callback.
+    """
+        dut = self.android_devices[0]
+
+        # Create 3 attach sessions: 2 without identity callback, 1 with
+        id1 = dut.droid.wifiAwareAttach(False, None, True)
+        time.sleep(10)  # to make sure all calls and callbacks are done
+        id2 = dut.droid.wifiAwareAttach(True, None, True)
+        time.sleep(10)  # to make sure all calls and callbacks are done
+        id3 = dut.droid.wifiAwareAttach(False, None, True)
+        dut.log.info('id1=%d, id2=%d, id3=%d', id1, id2, id3)
+
+        # Attach session 1: wait for attach, should not get identity
+        autils.wait_for_event(
+            dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, id1))
+        autils.fail_on_event(
+            dut,
+            autils.decorate_event(aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id1))
+
+        # Attach session 2: wait for attach and for identity callback
+        autils.wait_for_event(
+            dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, id2))
+        autils.wait_for_event(
+            dut,
+            autils.decorate_event(aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id2))
+
+        # Attach session 3: wait for attach, should not get identity
+        autils.wait_for_event(
+            dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, id3))
+        autils.fail_on_event(
+            dut,
+            autils.decorate_event(aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id3))
+
+    @test_tracker_info(uuid="b8ea4d02-ae23-42a7-a85e-def52932c858")
+    def test_attach_with_no_wifi(self):
+        """Function test case / Attach test cases / attempt to attach with wifi off
+
+    Validates that if trying to attach with Wi-Fi disabled will receive the
+    expected failure callback. As a side-effect also validates that the
+    broadcast for Aware unavailable is received.
+    """
+        dut = self.android_devices[0]
+        wutils.wifi_toggle_state(dut, False)
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+        dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+
+    @test_tracker_info(uuid="7dcc4530-c936-4447-9d22-a7c5b315e2ce")
+    def test_attach_with_doze(self):
+        """Function test case / Attach test cases / attempt to attach with doze on
+
+    Validates that if trying to attach with device in doze mode will receive the
+    expected failure callback. As a side-effect also validates that the
+    broadcast for Aware unavailable is received.
+    """
+        dut = self.android_devices[0]
+        asserts.assert_true(utils.enable_doze(dut), "Can't enable doze")
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+        dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+        asserts.assert_true(utils.disable_doze(dut), "Can't disable doze")
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+    @test_tracker_info(uuid="2574fd01-8974-4dd0-aeb8-a7194461140e")
+    def test_attach_with_location_off(self):
+        """Function test case / Attach test cases / attempt to attach with location
+    mode off.
+
+    Validates that if trying to attach with device location mode off will
+    receive the expected failure callback. As a side-effect also validates that
+    the broadcast for Aware unavailable is received.
+    """
+        dut = self.android_devices[0]
+        utils.set_location_service(dut, False)
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+        dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+        utils.set_location_service(dut, True)
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+    @test_tracker_info(uuid="7ffde8e7-a010-4b77-97f5-959f263b5249")
+    def test_attach_apm_toggle_attach_again(self):
+        """Validates that enabling Airplane mode while Aware is on resets it
+    correctly, and allows it to be re-enabled when Airplane mode is then
+    disabled."""
+        dut = self.android_devices[0]
+
+        # enable Aware (attach)
+        dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # enable airplane mode
+        utils.force_airplane_mode(dut, True)
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+        # wait a few seconds and disable airplane mode
+        time.sleep(10)
+        utils.force_airplane_mode(dut, False)
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+        # try enabling Aware again (attach)
+        dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
diff --git a/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py b/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py
new file mode 100644
index 0000000..3bed77f
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py
@@ -0,0 +1,275 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class CapabilitiesTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware Capabilities - verifying that the provided
+  capabilities are real (i.e. available)."""
+
+    SERVICE_NAME = "GoogleTestXYZ"
+
+    def create_config(self, dtype, service_name):
+        """Create a discovery configuration based on input parameters.
+
+    Args:
+      dtype: Publish or Subscribe discovery type
+      service_name: Service name.
+
+    Returns:
+      Discovery configuration object.
+    """
+        config = {}
+        config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+        config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+        return config
+
+    def start_discovery_session(self, dut, session_id, is_publish, dtype,
+                                service_name, expect_success):
+        """Start a discovery session
+
+    Args:
+      dut: Device under test
+      session_id: ID of the Aware session in which to start discovery
+      is_publish: True for a publish session, False for subscribe session
+      dtype: Type of the discovery session
+      service_name: Service name to use for the discovery session
+      expect_success: True if expect session to be created, False otherwise
+
+    Returns:
+      Discovery session ID.
+    """
+        config = {}
+        config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+        config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+
+        if is_publish:
+            disc_id = dut.droid.wifiAwarePublish(session_id, config)
+            event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+        else:
+            disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+            event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+        if expect_success:
+            autils.wait_for_event(dut, event_name)
+        else:
+            autils.wait_for_event(dut,
+                                  aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED)
+
+        return disc_id
+
+    ###############################
+
+    @test_tracker_info(uuid="45da8a41-6c02-4434-9eb9-aa0a36ff9f65")
+    def test_max_discovery_sessions(self):
+        """Validate that the device can create as many discovery sessions as are
+    indicated in the device capabilities
+    """
+        dut = self.android_devices[0]
+
+        # attach
+        session_id = dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        service_name_template = 'GoogleTestService-%s-%d'
+
+        # start the max number of publish sessions
+        for i in range(dut.aware_capabilities[aconsts.CAP_MAX_PUBLISHES]):
+            # create publish discovery session of both types
+            pub_disc_id = self.start_discovery_session(
+                dut, session_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+                if i % 2 == 0 else aconsts.PUBLISH_TYPE_SOLICITED,
+                service_name_template % ('pub', i), True)
+
+        # start the max number of subscribe sessions
+        for i in range(dut.aware_capabilities[aconsts.CAP_MAX_SUBSCRIBES]):
+            # create publish discovery session of both types
+            sub_disc_id = self.start_discovery_session(
+                dut, session_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+                if i % 2 == 0 else aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                service_name_template % ('sub', i), True)
+
+        # start another publish & subscribe and expect failure
+        self.start_discovery_session(
+            dut, session_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED,
+            service_name_template % ('pub', 900), False)
+        self.start_discovery_session(
+            dut, session_id, False, aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            service_name_template % ('pub', 901), False)
+
+        # delete one of the publishes and try again (see if can create subscribe
+        # instead - should not)
+        dut.droid.wifiAwareDestroyDiscoverySession(pub_disc_id)
+        self.start_discovery_session(
+            dut, session_id, False, aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            service_name_template % ('pub', 902), False)
+        self.start_discovery_session(
+            dut, session_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED,
+            service_name_template % ('pub', 903), True)
+
+        # delete one of the subscribes and try again (see if can create publish
+        # instead - should not)
+        dut.droid.wifiAwareDestroyDiscoverySession(sub_disc_id)
+        self.start_discovery_session(
+            dut, session_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED,
+            service_name_template % ('pub', 904), False)
+        self.start_discovery_session(
+            dut, session_id, False, aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            service_name_template % ('pub', 905), True)
+
+    def test_max_ndp(self):
+        """Validate that the device can create as many NDPs as are specified
+    by its capabilities.
+
+    Mechanics:
+    - Publisher on DUT (first device)
+    - Subscribers on all other devices
+    - On discovery set up NDP
+
+    Note: the test requires MAX_NDP + 2 devices to be validated. If these are
+    not available the test will fail.
+    """
+        dut = self.android_devices[0]
+
+        # get max NDP: using first available device (assumes all devices are the
+        # same)
+        max_ndp = dut.aware_capabilities[aconsts.CAP_MAX_NDP_SESSIONS]
+
+        # get number of attached devices: needs to be max_ndp+2 to allow for max_ndp
+        # NDPs + an additional one expected to fail.
+        # However, will run the test with max_ndp+1 devices to verify that at least
+        # that many NDPs can be created. Will still fail at the end to indicate that
+        # full test was not run.
+        num_peer_devices = min(len(self.android_devices) - 1, max_ndp + 1)
+        asserts.assert_true(
+            num_peer_devices >= max_ndp,
+            'A minimum of %d devices is needed to run the test, have %d' %
+            (max_ndp + 1, len(self.android_devices)))
+
+        # attach
+        session_id = dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # start publisher
+        p_disc_id = self.start_discovery_session(
+            dut,
+            session_id,
+            is_publish=True,
+            dtype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            service_name=self.SERVICE_NAME,
+            expect_success=True)
+
+        # loop over other DUTs
+        for i in range(num_peer_devices):
+            other_dut = self.android_devices[i + 1]
+
+            # attach
+            other_session_id = other_dut.droid.wifiAwareAttach()
+            autils.wait_for_event(other_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+            # start subscriber
+            s_disc_id = self.start_discovery_session(
+                other_dut,
+                other_session_id,
+                is_publish=False,
+                dtype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                service_name=self.SERVICE_NAME,
+                expect_success=True)
+
+            discovery_event = autils.wait_for_event(
+                other_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+            peer_id_on_sub = discovery_event['data'][
+                aconsts.SESSION_CB_KEY_PEER_ID]
+
+            # Subscriber: send message to peer (Publisher - so it knows our address)
+            other_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                                 self.get_next_msg_id(),
+                                                 "ping",
+                                                 aconsts.MAX_TX_RETRIES)
+            autils.wait_for_event(other_dut,
+                                  aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+            # Publisher: wait for received message
+            pub_rx_msg_event = autils.wait_for_event(
+                dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+            peer_id_on_pub = pub_rx_msg_event['data'][
+                aconsts.SESSION_CB_KEY_PEER_ID]
+
+            # publisher (responder): request network
+            p_req_key = autils.request_network(
+                dut,
+                dut.droid.wifiAwareCreateNetworkSpecifier(
+                    p_disc_id, peer_id_on_pub))
+
+            # subscriber (initiator): request network
+            s_req_key = autils.request_network(
+                other_dut,
+                other_dut.droid.wifiAwareCreateNetworkSpecifier(
+                    s_disc_id, peer_id_on_sub))
+
+            # wait for network (or not - on the last iteration)
+            if i != max_ndp:
+                p_net_event = autils.wait_for_event_with_keys(
+                    dut, cconsts.EVENT_NETWORK_CALLBACK,
+                    autils.EVENT_NDP_TIMEOUT,
+                    (cconsts.NETWORK_CB_KEY_EVENT,
+                     cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                    (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+                s_net_event = autils.wait_for_event_with_keys(
+                    other_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                    autils.EVENT_NDP_TIMEOUT,
+                    (cconsts.NETWORK_CB_KEY_EVENT,
+                     cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                    (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+                p_aware_if = p_net_event['data'][
+                    cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+                s_aware_if = s_net_event['data'][
+                    cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+                self.log.info('Interface names: p=%s, s=%s', p_aware_if,
+                              s_aware_if)
+
+                p_ipv6 = dut.droid.connectivityGetLinkLocalIpv6Address(
+                    p_aware_if).split('%')[0]
+                s_ipv6 = other_dut.droid.connectivityGetLinkLocalIpv6Address(
+                    s_aware_if).split('%')[0]
+                self.log.info('Interface addresses (IPv6): p=%s, s=%s', p_ipv6,
+                              s_ipv6)
+            else:
+                autils.fail_on_event_with_keys(
+                    dut, cconsts.EVENT_NETWORK_CALLBACK,
+                    autils.EVENT_NDP_TIMEOUT,
+                    (cconsts.NETWORK_CB_KEY_EVENT,
+                     cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                    (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+                autils.fail_on_event_with_keys(
+                    other_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                    autils.EVENT_NDP_TIMEOUT,
+                    (cconsts.NETWORK_CB_KEY_EVENT,
+                     cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                    (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+        asserts.assert_true(
+            num_peer_devices > max_ndp,
+            'Needed %d devices to run the test, have %d' %
+            (max_ndp + 2, len(self.android_devices)))
diff --git a/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py b/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
new file mode 100644
index 0000000..b8ac693
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
@@ -0,0 +1,2298 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DataPathTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware data-path."""
+
+    # configuration parameters used by tests
+    ENCR_TYPE_OPEN = 0
+    ENCR_TYPE_PASSPHRASE = 1
+    ENCR_TYPE_PMK = 2
+
+    PASSPHRASE = "This is some random passphrase - very very secure!!"
+    PASSPHRASE_MIN = "01234567"
+    PASSPHRASE_MAX = "012345678901234567890123456789012345678901234567890123456789012"
+    PMK = "ODU0YjE3YzdmNDJiNWI4NTQ2NDJjNDI3M2VkZTQyZGU="
+    PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
+    PMK2 = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="
+
+    PING_MSG = "ping"
+
+    # message re-transmit counter (increases reliability in open-environment)
+    # Note: reliability of message transmission is tested elsewhere
+    MSG_RETX_COUNT = 5  # hard-coded max value, internal API
+
+    # number of second to 'reasonably' wait to make sure that devices synchronize
+    # with each other - useful for OOB test cases, where the OOB discovery would
+    # take some time
+    WAIT_FOR_CLUSTER = 5
+
+    def create_config(self, dtype):
+        """Create a base configuration based on input parameters.
+
+    Args:
+      dtype: Publish or Subscribe discovery type
+
+    Returns:
+      Discovery configuration object.
+    """
+        config = {}
+        config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+        config[
+            aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceDataPath"
+        return config
+
+    def request_network(self, dut, ns):
+        """Request a Wi-Fi Aware network.
+
+    Args:
+      dut: Device
+      ns: Network specifier
+    Returns: the request key
+    """
+        network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+        return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+    def set_up_discovery(self,
+                         ptype,
+                         stype,
+                         get_peer_id,
+                         pub_on_both=False,
+                         pub_on_both_same=True):
+        """Set up discovery sessions and wait for service discovery.
+
+    Args:
+      ptype: Publish discovery type
+      stype: Subscribe discovery type
+      get_peer_id: Send a message across to get the peer's id
+      pub_on_both: If True then set up a publisher on both devices. The second
+                   publisher isn't used (existing to test use-case).
+      pub_on_both_same: If True then the second publish uses an identical
+                        service name, otherwise a different service name.
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach()
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach()
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: start publish and wait for confirmation
+        p_disc_id = p_dut.droid.wifiAwarePublish(p_id,
+                                                 self.create_config(ptype))
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Optionally set up a publish session on the Subscriber device
+        if pub_on_both:
+            p2_config = self.create_config(ptype)
+            if not pub_on_both_same:
+                p2_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = (
+                    p2_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] + "-XYZXYZ")
+            s_dut.droid.wifiAwarePublish(s_id, p2_config)
+            autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Subscriber: start subscribe and wait for confirmation
+        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id,
+                                                   self.create_config(stype))
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # Subscriber: wait for service discovery
+        discovery_event = autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        peer_id_on_sub = discovery_event["data"][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+
+        peer_id_on_pub = None
+        if get_peer_id:  # only need message to receive peer ID
+            # Subscriber: send message to peer (Publisher - so it knows our address)
+            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                             self.get_next_msg_id(),
+                                             self.PING_MSG,
+                                             self.MSG_RETX_COUNT)
+            autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+            # Publisher: wait for received message
+            pub_rx_msg_event = autils.wait_for_event(
+                p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+            peer_id_on_pub = pub_rx_msg_event["data"][
+                aconsts.SESSION_CB_KEY_PEER_ID]
+
+        return (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+                peer_id_on_pub)
+
+    def run_ib_data_path_test(self,
+                              ptype,
+                              stype,
+                              encr_type,
+                              use_peer_id,
+                              passphrase_to_use=None,
+                              pub_on_both=False,
+                              pub_on_both_same=True,
+                              expect_failure=False):
+        """Runs the in-band data-path tests.
+
+    Args:
+      ptype: Publish discovery type
+      stype: Subscribe discovery type
+      encr_type: Encryption type, one of ENCR_TYPE_*
+      use_peer_id: On Responder (publisher): True to use peer ID, False to
+                   accept any request
+      passphrase_to_use: The passphrase to use if encr_type=ENCR_TYPE_PASSPHRASE
+                         If None then use self.PASSPHRASE
+      pub_on_both: If True then set up a publisher on both devices. The second
+                   publisher isn't used (existing to test use-case).
+      pub_on_both_same: If True then the second publish uses an identical
+                        service name, otherwise a different service name.
+      expect_failure: If True then don't expect NDP formation, otherwise expect
+                      NDP setup to succeed.
+    """
+        (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+         peer_id_on_pub) = self.set_up_discovery(
+             ptype,
+             stype,
+             use_peer_id,
+             pub_on_both=pub_on_both,
+             pub_on_both_same=pub_on_both_same)
+
+        passphrase = None
+        pmk = None
+        if encr_type == self.ENCR_TYPE_PASSPHRASE:
+            passphrase = (self.PASSPHRASE
+                          if passphrase_to_use == None else passphrase_to_use)
+        elif encr_type == self.ENCR_TYPE_PMK:
+            pmk = self.PMK
+
+        port = 1234
+        transport_protocol = 6  # TCP/IP
+
+        # Publisher: request network
+        if encr_type == self.ENCR_TYPE_OPEN:
+            p_req_key = self.request_network(
+                p_dut,
+                p_dut.droid.wifiAwareCreateNetworkSpecifier(
+                    p_disc_id, peer_id_on_pub
+                    if use_peer_id else None, passphrase, pmk))
+        else:
+            p_req_key = self.request_network(
+                p_dut,
+                p_dut.droid.wifiAwareCreateNetworkSpecifier(
+                    p_disc_id, peer_id_on_pub if use_peer_id else None,
+                    passphrase, pmk, port, transport_protocol))
+
+        # Subscriber: request network
+        s_req_key = self.request_network(
+            s_dut,
+            s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                s_disc_id, peer_id_on_sub, passphrase, pmk))
+
+        if expect_failure:
+            # Publisher & Subscriber: expect unavailable callbacks
+            autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+            autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+        else:
+            # Publisher & Subscriber: wait for network formation
+            p_net_event_nc = autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            s_net_event_nc = autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+            # validate no leak of information
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in p_net_event_nc[
+                    "data"], "Network specifier leak!")
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in s_net_event_nc[
+                    "data"], "Network specifier leak!")
+
+            # note that Pub <-> Sub since IPv6 are of peer's!
+            s_ipv6 = p_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+            p_ipv6 = s_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+            self.verify_network_info(
+                p_net_event_nc["data"], s_net_event_nc["data"],
+                encr_type == self.ENCR_TYPE_OPEN, port, transport_protocol)
+
+            p_net_event_lp = autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            s_net_event_lp = autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+            p_aware_if = p_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+            s_aware_if = s_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+            self.log.info("Interface names: p=%s, s=%s", p_aware_if,
+                          s_aware_if)
+            self.log.info("Interface addresses (IPv6): p=%s, s=%s", p_ipv6,
+                          s_ipv6)
+
+            # open sockets to test connection
+            asserts.assert_true(
+                autils.verify_socket_connect(p_dut, s_dut, p_ipv6, s_ipv6, 0),
+                "Failed socket link with Pub as Server")
+            asserts.assert_true(
+                autils.verify_socket_connect(s_dut, p_dut, s_ipv6, p_ipv6, 0),
+                "Failed socket link with Sub as Server")
+
+            # terminate sessions and wait for ON_LOST callbacks
+            p_dut.droid.wifiAwareDestroy(p_id)
+            s_dut.droid.wifiAwareDestroy(s_id)
+
+            autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+        # clean-up
+        p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+    def run_oob_data_path_test(self,
+                               encr_type,
+                               use_peer_id,
+                               setup_discovery_sessions=False,
+                               expect_failure=False):
+        """Runs the out-of-band data-path tests.
+
+    Args:
+      encr_type: Encryption type, one of ENCR_TYPE_*
+      use_peer_id: On Responder: True to use peer ID, False to accept any
+                   request
+      setup_discovery_sessions: If True also set up a (spurious) discovery
+        session (pub on both sides, sub on Responder side). Validates a corner
+        case.
+      expect_failure: If True then don't expect NDP formation, otherwise expect
+                      NDP setup to succeed.
+    """
+        init_dut = self.android_devices[0]
+        init_dut.pretty_name = "Initiator"
+        resp_dut = self.android_devices[1]
+        resp_dut.pretty_name = "Responder"
+
+        # Initiator+Responder: attach and wait for confirmation & identity
+        init_id = init_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        init_ident_event = autils.wait_for_event(
+            init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        init_mac = init_ident_event["data"]["mac"]
+        time.sleep(self.device_startup_offset)
+        resp_id = resp_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        resp_ident_event = autils.wait_for_event(
+            resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        resp_mac = resp_ident_event["data"]["mac"]
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(self.WAIT_FOR_CLUSTER)
+
+        if setup_discovery_sessions:
+            init_dut.droid.wifiAwarePublish(
+                init_id, self.create_config(aconsts.PUBLISH_TYPE_UNSOLICITED))
+            autils.wait_for_event(init_dut,
+                                  aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+            resp_dut.droid.wifiAwarePublish(
+                resp_id, self.create_config(aconsts.PUBLISH_TYPE_UNSOLICITED))
+            autils.wait_for_event(resp_dut,
+                                  aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+            resp_dut.droid.wifiAwareSubscribe(
+                resp_id, self.create_config(aconsts.SUBSCRIBE_TYPE_PASSIVE))
+            autils.wait_for_event(resp_dut,
+                                  aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+            autils.wait_for_event(resp_dut,
+                                  aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+        passphrase = None
+        pmk = None
+        if encr_type == self.ENCR_TYPE_PASSPHRASE:
+            passphrase = self.PASSPHRASE
+        elif encr_type == self.ENCR_TYPE_PMK:
+            pmk = self.PMK
+
+        # Responder: request network
+        resp_req_key = self.request_network(
+            resp_dut,
+            resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                resp_id, aconsts.DATA_PATH_RESPONDER, init_mac
+                if use_peer_id else None, passphrase, pmk))
+
+        # Initiator: request network
+        init_req_key = self.request_network(
+            init_dut,
+            init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, passphrase,
+                pmk))
+
+        if expect_failure:
+            # Initiator & Responder: expect unavailable callbacks
+            autils.wait_for_event_with_keys(
+                init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+            autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+        else:
+            # Initiator & Responder: wait for network formation
+            init_net_event_nc = autils.wait_for_event_with_keys(
+                init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+            resp_net_event_nc = autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+            # validate no leak of information
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in init_net_event_nc[
+                    "data"], "Network specifier leak!")
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in resp_net_event_nc[
+                    "data"], "Network specifier leak!")
+
+            # note that Init <-> Resp since IPv6 are of peer's!
+            init_ipv6 = resp_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+            resp_ipv6 = init_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+            init_net_event_lp = autils.wait_for_event_with_keys(
+                init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+            resp_net_event_lp = autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+            init_aware_if = init_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+            resp_aware_if = resp_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+            self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                          resp_aware_if)
+            self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                          resp_ipv6)
+
+            # open sockets to test connection
+            asserts.assert_true(
+                autils.verify_socket_connect(init_dut, resp_dut, init_ipv6,
+                                             resp_ipv6, 0),
+                "Failed socket link with Initiator as Server")
+            asserts.assert_true(
+                autils.verify_socket_connect(resp_dut, init_dut, resp_ipv6,
+                                             init_ipv6, 0),
+                "Failed socket link with Responder as Server")
+
+            # terminate sessions and wait for ON_LOST callbacks
+            init_dut.droid.wifiAwareDestroy(init_id)
+            resp_dut.droid.wifiAwareDestroy(resp_id)
+
+            autils.wait_for_event_with_keys(
+                init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST),
+                (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+            autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST),
+                (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+        # clean-up
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    def run_mismatched_ib_data_path_test(self, pub_mismatch, sub_mismatch):
+        """Runs the negative in-band data-path tests: mismatched peer ID.
+
+    Args:
+      pub_mismatch: Mismatch the publisher's ID
+      sub_mismatch: Mismatch the subscriber's ID
+    """
+        (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+         peer_id_on_pub) = self.set_up_discovery(
+             aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE,
+             True)
+
+        if pub_mismatch:
+            peer_id_on_pub = peer_id_on_pub - 1
+        if sub_mismatch:
+            peer_id_on_sub = peer_id_on_sub - 1
+
+        # Publisher: request network
+        p_req_key = self.request_network(
+            p_dut,
+            p_dut.droid.wifiAwareCreateNetworkSpecifier(
+                p_disc_id, peer_id_on_pub, None))
+
+        # Subscriber: request network
+        s_req_key = self.request_network(
+            s_dut,
+            s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                s_disc_id, peer_id_on_sub, None))
+
+        # Publisher & Subscriber:
+        # - expect unavailable callbacks on the party with the bad ID
+        # - also expect unavailable on the Initiator party (i.e. the
+        #   Subscriber) if the Publisher has a bad ID
+        # - but a Publisher with a valid ID will keep waiting ...
+        autils.wait_for_event_with_keys(
+            s_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+        if pub_mismatch:
+            autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+        else:
+            time.sleep(autils.EVENT_NDP_TIMEOUT)
+            autils.fail_on_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK, 0,
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+
+        # clean-up
+        p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+    def run_mismatched_oob_data_path_test(self,
+                                          init_mismatch_mac=False,
+                                          resp_mismatch_mac=False,
+                                          init_encr_type=ENCR_TYPE_OPEN,
+                                          resp_encr_type=ENCR_TYPE_OPEN):
+        """Runs the negative out-of-band data-path tests: mismatched information
+    between Responder and Initiator.
+
+    Args:
+      init_mismatch_mac: True to mismatch the Initiator MAC address
+      resp_mismatch_mac: True to mismatch the Responder MAC address
+      init_encr_type: Encryption type of Initiator - ENCR_TYPE_*
+      resp_encr_type: Encryption type of Responder - ENCR_TYPE_*
+    """
+        init_dut = self.android_devices[0]
+        init_dut.pretty_name = "Initiator"
+        resp_dut = self.android_devices[1]
+        resp_dut.pretty_name = "Responder"
+
+        # Initiator+Responder: attach and wait for confirmation & identity
+        init_id = init_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        init_ident_event = autils.wait_for_event(
+            init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        init_mac = init_ident_event["data"]["mac"]
+        time.sleep(self.device_startup_offset)
+        resp_id = resp_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        resp_ident_event = autils.wait_for_event(
+            resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        resp_mac = resp_ident_event["data"]["mac"]
+
+        if init_mismatch_mac:  # assumes legit ones don't start with "00"
+            init_mac = "00" + init_mac[2:]
+        if resp_mismatch_mac:
+            resp_mac = "00" + resp_mac[2:]
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(self.WAIT_FOR_CLUSTER)
+
+        # set up separate keys: even if types are the same we want a mismatch
+        init_passphrase = None
+        init_pmk = None
+        if init_encr_type == self.ENCR_TYPE_PASSPHRASE:
+            init_passphrase = self.PASSPHRASE
+        elif init_encr_type == self.ENCR_TYPE_PMK:
+            init_pmk = self.PMK
+
+        resp_passphrase = None
+        resp_pmk = None
+        if resp_encr_type == self.ENCR_TYPE_PASSPHRASE:
+            resp_passphrase = self.PASSPHRASE2
+        elif resp_encr_type == self.ENCR_TYPE_PMK:
+            resp_pmk = self.PMK2
+
+        # Responder: request network
+        resp_req_key = self.request_network(
+            resp_dut,
+            resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                resp_id, aconsts.DATA_PATH_RESPONDER, init_mac,
+                resp_passphrase, resp_pmk))
+
+        # Initiator: request network
+        init_req_key = self.request_network(
+            init_dut,
+            init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                init_id, aconsts.DATA_PATH_INITIATOR, resp_mac,
+                init_passphrase, init_pmk))
+
+        # Initiator & Responder:
+        # - expect unavailable on the Initiator party if the
+        #   Initiator and Responder with mac or encryption mismatch
+        # - For responder:
+        #   - If mac mismatch, responder will keep waiting ...
+        #   - If encryption mismatch, responder expect unavailable
+        autils.wait_for_event_with_keys(
+            init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+        time.sleep(autils.EVENT_NDP_TIMEOUT)
+        if init_mismatch_mac or resp_mismatch_mac:
+            autils.fail_on_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK, 0,
+                (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+        else:
+            autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
+
+        # clean-up
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    def verify_network_info(self, p_data, s_data, open, port,
+                            transport_protocol):
+        """Verify that the port and transport protocol information is correct.
+            - should only exist on subscriber (received from publisher)
+              and match transmitted values
+            - should only exist on an encrypted NDP
+
+        Args:
+            p_data, s_data: Pub and Sub (respectively) net cap event data.
+            open: True if NDP unencrypted, False if encrypted.
+            port: Expected port value.
+            transport_protocol: Expected transport protocol value.
+        """
+        asserts.assert_true(aconsts.NET_CAP_PORT not in p_data,
+                            "port info not expected on Pub")
+        asserts.assert_true(aconsts.NET_CAP_TRANSPORT_PROTOCOL not in p_data,
+                            "transport protocol info not expected on Pub")
+        if open:
+            asserts.assert_true(aconsts.NET_CAP_PORT not in s_data,
+                                "port info not expected on Sub (open NDP)")
+            asserts.assert_true(
+                aconsts.NET_CAP_TRANSPORT_PROTOCOL not in s_data,
+                "transport protocol info not expected on Sub (open NDP)")
+        else:
+            asserts.assert_equal(s_data[aconsts.NET_CAP_PORT], port,
+                                 "Port info does not match on Sub (from Pub)")
+            asserts.assert_equal(
+                s_data[aconsts.NET_CAP_TRANSPORT_PROTOCOL], transport_protocol,
+                "Transport protocol info does not match on Sub (from Pub)")
+
+    #######################################
+    # Positive In-Band (IB) tests key:
+    #
+    # names is: test_ib_<pub_type>_<sub_type>_<encr_type>_<peer_spec>
+    # where:
+    #
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    # encr_type: Encription type: open, passphrase
+    # peer_spec: Peer specification method: any or specific
+    #
+    # Note: In-Band means using Wi-Fi Aware for discovery and referring to the
+    # peer using the Aware-provided peer handle (as opposed to a MAC address).
+    #######################################
+
+    @test_tracker_info(uuid="fa30bedc-d1de-4440-bf25-ec00d10555af")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_unsolicited_passive_open_specific(self):
+        """Data-path: in-band, unsolicited/passive, open encryption, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=True)
+
+    @test_tracker_info(uuid="57fc9d53-32ae-470f-a8b1-2fe37893687d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_unsolicited_passive_open_any(self):
+        """Data-path: in-band, unsolicited/passive, open encryption, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False)
+
+    @test_tracker_info(uuid="93b2a23d-8579-448a-936c-7812929464cf")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_unsolicited_passive_passphrase_specific(self):
+        """Data-path: in-band, unsolicited/passive, passphrase, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_PASSPHRASE,
+            use_peer_id=True)
+
+    @test_tracker_info(uuid="1736126f-a0ff-4712-acc4-f89b4eef5716")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_unsolicited_passive_passphrase_any(self):
+        """Data-path: in-band, unsolicited/passive, passphrase, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_PASSPHRASE,
+            use_peer_id=False)
+
+    @test_tracker_info(uuid="b9353d5b-3f77-46bf-bfd9-65d56a7c939a")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_unsolicited_passive_pmk_specific(self):
+        """Data-path: in-band, unsolicited/passive, PMK, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_PMK,
+            use_peer_id=True)
+
+    @test_tracker_info(uuid="06f3b2ab-4a10-4398-83a4-6a23851b1662")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_unsolicited_passive_pmk_any(self):
+        """Data-path: in-band, unsolicited/passive, PMK, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_PMK,
+            use_peer_id=False)
+
+    @test_tracker_info(uuid="0ed7d8b3-a69e-46ba-aeb7-13e507ecf290")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_solicited_active_open_specific(self):
+        """Data-path: in-band, solicited/active, open encryption, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=True)
+
+    @test_tracker_info(uuid="c7ba6d28-5ef6-45d9-95d5-583ad6d981f3")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_solicited_active_open_any(self):
+        """Data-path: in-band, solicited/active, open encryption, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False)
+
+    @test_tracker_info(uuid="388cea99-0e2e-49ea-b00e-f3e56b6236e5")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_solicited_active_passphrase_specific(self):
+        """Data-path: in-band, solicited/active, passphrase, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            encr_type=self.ENCR_TYPE_PASSPHRASE,
+            use_peer_id=True)
+
+    @test_tracker_info(uuid="fcd3e28a-5eab-4169-8a0c-dc7204dcdc13")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_solicited_active_passphrase_any(self):
+        """Data-path: in-band, solicited/active, passphrase, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            encr_type=self.ENCR_TYPE_PASSPHRASE,
+            use_peer_id=False)
+
+    @test_tracker_info(uuid="9d4eaad7-ba53-4a06-8ce0-e308daea3309")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_solicited_active_pmk_specific(self):
+        """Data-path: in-band, solicited/active, PMK, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            encr_type=self.ENCR_TYPE_PMK,
+            use_peer_id=True)
+
+    @test_tracker_info(uuid="129d850e-c312-4137-a67b-05ae95fe66cc")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_solicited_active_pmk_any(self):
+        """Data-path: in-band, solicited/active, PMK, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            encr_type=self.ENCR_TYPE_PMK,
+            use_peer_id=False)
+
+    #######################################
+    # Positive In-Band (IB) with a publish session running on the subscriber
+    # tests key:
+    #
+    # names is: test_ib_extra_pub_<same|diff>_<pub_type>_<sub_type>
+    #                                          _<encr_type>_<peer_spec>
+    # where:
+    #
+    # same|diff: Whether the extra publish session (on the subscriber) is the same
+    #            or different from the primary session.
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    # encr_type: Encryption type: open, passphrase
+    # peer_spec: Peer specification method: any or specific
+    #
+    # Note: In-Band means using Wi-Fi Aware for discovery and referring to the
+    # peer using the Aware-provided peer handle (as opposed to a MAC address).
+    #######################################
+
+    @test_tracker_info(uuid="e855dd81-45c8-4bb2-a204-7687c48ff843")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_extra_pub_same_unsolicited_passive_open_specific(self):
+        """Data-path: in-band, unsolicited/passive, open encryption, specific peer.
+
+    Configuration contains a publisher (for the same service) running on *both*
+    devices.
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=True,
+            pub_on_both=True,
+            pub_on_both_same=True)
+
+    @test_tracker_info(uuid="228ea657-82e6-44bc-8369-a2c719a5e252")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_extra_pub_same_unsolicited_passive_open_any(self):
+        """Data-path: in-band, unsolicited/passive, open encryption, any peer.
+
+    Configuration contains a publisher (for the same service) running on *both*
+    devices.
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False,
+            pub_on_both=True,
+            pub_on_both_same=True)
+
+    @test_tracker_info(uuid="7a32f439-d745-4716-a75e-b54109aaaf82")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_extra_pub_diff_unsolicited_passive_open_specific(self):
+        """Data-path: in-band, unsolicited/passive, open encryption, specific peer.
+
+    Configuration contains a publisher (for a different service) running on
+    *both* devices.
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=True,
+            pub_on_both=True,
+            pub_on_both_same=False)
+
+    @test_tracker_info(uuid="a14ddc66-88fd-4b49-ab37-225533867c63")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_extra_pub_diff_unsolicited_passive_open_any(self):
+        """Data-path: in-band, unsolicited/passive, open encryption, any peer.
+
+    Configuration contains a publisher (for a different service) running on
+    *both* devices.
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False,
+            pub_on_both=True,
+            pub_on_both_same=False)
+
+    #######################################
+    # Positive Out-of-Band (OOB) tests key:
+    #
+    # names is: test_oob_<encr_type>_<peer_spec>
+    # where:
+    #
+    # encr_type: Encryption type: open, passphrase
+    # peer_spec: Peer specification method: any or specific
+    #
+    # Optionally set up an extra discovery session to test coexistence. If so
+    # add "ib_coex" to test name.
+    #
+    # Note: Out-of-Band means using a non-Wi-Fi Aware mechanism for discovery and
+    # exchange of MAC addresses and then Wi-Fi Aware for data-path.
+    #######################################
+
+    @test_tracker_info(uuid="7db17d8c-1dce-4084-b695-215bbcfe7d41")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_open_specific(self):
+        """Data-path: out-of-band, open encryption, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_OPEN, use_peer_id=True)
+
+    @test_tracker_info(uuid="ad416d89-cb95-4a07-8d29-ee213117450b")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_open_any(self):
+        """Data-path: out-of-band, open encryption, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_OPEN, use_peer_id=False)
+
+    @test_tracker_info(uuid="74937a3a-d524-43e2-8979-4449271cab52")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_passphrase_specific(self):
+        """Data-path: out-of-band, passphrase, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_PASSPHRASE, use_peer_id=True)
+
+    @test_tracker_info(uuid="afcbdc7e-d3a9-465b-b1da-ce2e42e3941e")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_passphrase_any(self):
+        """Data-path: out-of-band, passphrase, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_PASSPHRASE, use_peer_id=False)
+
+    @test_tracker_info(uuid="0d095031-160a-4537-aab5-41b6ad5d55f8")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_pmk_specific(self):
+        """Data-path: out-of-band, PMK, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_PMK, use_peer_id=True)
+
+    @test_tracker_info(uuid="e45477bd-66cc-4eb7-88dd-4518c8aa2a74")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_pmk_any(self):
+        """Data-path: out-of-band, PMK, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_PMK, use_peer_id=False)
+
+    @test_tracker_info(uuid="dd464f24-b404-4eea-955c-d10c9e8adefc")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_ib_coex_open_specific(self):
+        """Data-path: out-of-band, open encryption, specific peer - in-band coex:
+    set up a concurrent discovery session to verify no impact. The session
+    consists of Publisher on both ends, and a Subscriber on the Responder.
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=True,
+            setup_discovery_sessions=True)
+
+    @test_tracker_info(uuid="088fcd3a-b015-4179-a9a5-91f782b03e3b")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_ib_coex_open_any(self):
+        """Data-path: out-of-band, open encryption, any peer - in-band coex:
+    set up a concurrent discovery session to verify no impact. The session
+    consists of Publisher on both ends, and a Subscriber on the Responder.
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False,
+            setup_discovery_sessions=True)
+
+    ##############################################################
+
+    @test_tracker_info(uuid="1c2c9805-dc1e-43b5-a1b8-315e8c9a4337")
+    @WifiBaseTest.wifi_test_wrap
+    def test_passphrase_min(self):
+        """Data-path: minimum passphrase length
+
+    Use in-band, unsolicited/passive, any peer combination
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_PASSPHRASE,
+            use_peer_id=False,
+            passphrase_to_use=self.PASSPHRASE_MIN)
+
+    @test_tracker_info(uuid="e696e2b9-87a9-4521-b337-61b9efaa2057")
+    @WifiBaseTest.wifi_test_wrap
+    def test_passphrase_max(self):
+        """Data-path: maximum passphrase length
+
+    Use in-band, unsolicited/passive, any peer combination
+    """
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_PASSPHRASE,
+            use_peer_id=False,
+            passphrase_to_use=self.PASSPHRASE_MAX)
+
+    @test_tracker_info(uuid="533cd44c-ff30-4283-ac28-f71fd7b4f02d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_publisher_peer_id(self):
+        """Data-path: failure when publisher peer ID is mismatched"""
+        self.run_mismatched_ib_data_path_test(
+            pub_mismatch=True, sub_mismatch=False)
+
+    @test_tracker_info(uuid="682f275e-722a-4f8b-85e7-0dcea9d25532")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_subscriber_peer_id(self):
+        """Data-path: failure when subscriber peer ID is mismatched"""
+        self.run_mismatched_ib_data_path_test(
+            pub_mismatch=False, sub_mismatch=True)
+
+    @test_tracker_info(uuid="7fa82796-7fc9-4d9e-bbbb-84b751788943")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_init_mac(self):
+        """Data-path: failure when Initiator MAC address mismatch"""
+        self.run_mismatched_oob_data_path_test(
+            init_mismatch_mac=True, resp_mismatch_mac=False)
+
+    @test_tracker_info(uuid="edeae959-4644-44f9-8d41-bdeb5216954e")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_resp_mac(self):
+        """Data-path: failure when Responder MAC address mismatch"""
+        self.run_mismatched_oob_data_path_test(
+            init_mismatch_mac=False, resp_mismatch_mac=True)
+
+    @test_tracker_info(uuid="91f46949-c47f-49f9-a90f-6fae699613a7")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_passphrase(self):
+        """Data-path: failure when passphrases mismatch"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+            resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+    @test_tracker_info(uuid="01c49c2e-dc92-4a27-bb47-c4fc67617c23")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_pmk(self):
+        """Data-path: failure when PMK mismatch"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_PMK,
+            resp_encr_type=self.ENCR_TYPE_PMK)
+
+    @test_tracker_info(uuid="4d651797-5fbb-408e-a4b6-a6e1944136da")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_open_passphrase(self):
+        """Data-path: failure when initiator is open, and responder passphrase"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_OPEN,
+            resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+    @test_tracker_info(uuid="1ae697f4-5987-4187-aeef-1e22d07d4a7c")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_open_pmk(self):
+        """Data-path: failure when initiator is open, and responder PMK"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_OPEN,
+            resp_encr_type=self.ENCR_TYPE_PMK)
+
+    @test_tracker_info(uuid="f027b1cc-0e7a-4075-b880-5e64b288afbd")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_pmk_passphrase(self):
+        """Data-path: failure when initiator is pmk, and responder passphrase"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_PMK,
+            resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+    @test_tracker_info(uuid="0819bbd4-72ae-49c4-bd46-5448db2b0a06")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_passphrase_open(self):
+        """Data-path: failure when initiator is passphrase, and responder open"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+            resp_encr_type=self.ENCR_TYPE_OPEN)
+
+    @test_tracker_info(uuid="7ef24f62-8e6b-4732-88a3-80a43584dda4")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_pmk_open(self):
+        """Data-path: failure when initiator is PMK, and responder open"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_PMK,
+            resp_encr_type=self.ENCR_TYPE_OPEN)
+
+    @test_tracker_info(uuid="7b9c9efc-1c06-465e-8a5e-d6a22ac1da97")
+    @WifiBaseTest.wifi_test_wrap
+    def test_negative_mismatch_passphrase_pmk(self):
+        """Data-path: failure when initiator is passphrase, and responder pmk"""
+        self.run_mismatched_oob_data_path_test(
+            init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+            resp_encr_type=self.ENCR_TYPE_OPEN)
+
+    ##########################################################################
+
+    def wait_for_request_responses(self, dut, req_keys, aware_ifs, aware_ipv6):
+        """Wait for network request confirmation for all request keys.
+
+    Args:
+      dut: Device under test
+      req_keys: (in) A list of the network requests
+      aware_ifs: (out) A list into which to append the network interface
+      aware_ipv6: (out) A list into which to append the network ipv6 address
+    """
+        num_events = 0  # looking for 2 events per NDP: link-prop + net-cap
+        while num_events != 2 * len(req_keys):
+            event = autils.wait_for_event(
+                dut,
+                cconsts.EVENT_NETWORK_CALLBACK,
+                timeout=autils.EVENT_NDP_TIMEOUT)
+            if (event["data"][cconsts.NETWORK_CB_KEY_EVENT] ==
+                    cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+                if event["data"][cconsts.NETWORK_CB_KEY_ID] in req_keys:
+                    num_events = num_events + 1
+                    aware_ifs.append(
+                        event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
+                else:
+                    self.log.info(
+                        "Received an unexpected connectivity, the revoked "
+                        "network request probably went through -- %s", event)
+            elif (event["data"][cconsts.NETWORK_CB_KEY_EVENT] ==
+                  cconsts.NETWORK_CB_CAPABILITIES_CHANGED):
+                if event["data"][cconsts.NETWORK_CB_KEY_ID] in req_keys:
+                    num_events = num_events + 1
+                    aware_ipv6.append(event["data"][aconsts.NET_CAP_IPV6])
+                else:
+                    self.log.info(
+                        "Received an unexpected connectivity, the revoked "
+                        "network request probably went through -- %s", event)
+                # validate no leak of information
+                asserts.assert_false(
+                    cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in event["data"],
+                    "Network specifier leak!")
+
+    @test_tracker_info(uuid="2e325e2b-d552-4890-b470-20b40284395d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_identical_networks(self):
+        """Validate that creating multiple networks between 2 devices, each network
+    with identical configuration is supported over a single NDP.
+
+    Verify that the interface and IPv6 address is the same for all networks.
+    """
+        init_dut = self.android_devices[0]
+        init_dut.pretty_name = "Initiator"
+        resp_dut = self.android_devices[1]
+        resp_dut.pretty_name = "Responder"
+
+        N = 2  # first iteration (must be 2 to give us a chance to cancel the first)
+        M = 5  # second iteration
+
+        init_ids = []
+        resp_ids = []
+
+        # Initiator+Responder: attach and wait for confirmation & identity
+        # create N+M sessions to be used in the different (but identical) NDPs
+        for i in range(N + M):
+            id, init_mac = autils.attach_with_identity(init_dut)
+            init_ids.append(id)
+            id, resp_mac = autils.attach_with_identity(resp_dut)
+            resp_ids.append(id)
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        resp_req_keys = []
+        init_req_keys = []
+        resp_aware_ifs = []
+        init_aware_ifs = []
+        resp_aware_ipv6 = []
+        init_aware_ipv6 = []
+
+        # issue N quick requests for identical NDPs - without waiting for result
+        # tests whether pre-setup multiple NDP procedure
+        for i in range(N):
+            # Responder: request network
+            resp_req_keys.append(
+                autils.request_network(
+                    resp_dut,
+                    resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                        resp_ids[i], aconsts.DATA_PATH_RESPONDER, init_mac,
+                        None)))
+
+            # Initiator: request network
+            init_req_keys.append(
+                autils.request_network(
+                    init_dut,
+                    init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                        init_ids[i], aconsts.DATA_PATH_INITIATOR, resp_mac,
+                        None)))
+
+        # remove the first request (hopefully before completed) testing that NDP
+        # is still created
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_keys[0])
+        resp_req_keys.remove(resp_req_keys[0])
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_keys[0])
+        init_req_keys.remove(init_req_keys[0])
+
+        # wait for network formation for all initial requests
+        # note: for IPv6 Init <--> Resp since each reports the other's IPv6 address
+        #       in it's transport-specific network info
+        self.wait_for_request_responses(resp_dut, resp_req_keys,
+                                        resp_aware_ifs, init_aware_ipv6)
+        self.wait_for_request_responses(init_dut, init_req_keys,
+                                        init_aware_ifs, resp_aware_ipv6)
+
+        # issue M more requests for the same NDPs - tests post-setup multiple NDP
+        for i in range(M):
+            # Responder: request network
+            resp_req_keys.append(
+                autils.request_network(
+                    resp_dut,
+                    resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                        resp_ids[N + i], aconsts.DATA_PATH_RESPONDER, init_mac,
+                        None)))
+
+            # Initiator: request network
+            init_req_keys.append(
+                autils.request_network(
+                    init_dut,
+                    init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                        init_ids[N + i], aconsts.DATA_PATH_INITIATOR, resp_mac,
+                        None)))
+
+        # wait for network formation for all subsequent requests
+        self.wait_for_request_responses(resp_dut, resp_req_keys[N - 1:],
+                                        resp_aware_ifs, init_aware_ipv6)
+        self.wait_for_request_responses(init_dut, init_req_keys[N - 1:],
+                                        init_aware_ifs, resp_aware_ipv6)
+
+        # determine whether all interfaces and ipv6 addresses are identical
+        # (single NDP)
+        init_aware_ifs = list(set(init_aware_ifs))
+        resp_aware_ifs = list(set(resp_aware_ifs))
+        init_aware_ipv6 = list(set(init_aware_ipv6))
+        resp_aware_ipv6 = list(set(resp_aware_ipv6))
+
+        self.log.info("Interface names: I=%s, R=%s", init_aware_ifs,
+                      resp_aware_ifs)
+        self.log.info("Interface IPv6: I=%s, R=%s", init_aware_ipv6,
+                      resp_aware_ipv6)
+        self.log.info("Initiator requests: %s", init_req_keys)
+        self.log.info("Responder requests: %s", resp_req_keys)
+
+        asserts.assert_equal(
+            len(init_aware_ifs), 1, "Multiple initiator interfaces")
+        asserts.assert_equal(
+            len(resp_aware_ifs), 1, "Multiple responder interfaces")
+        asserts.assert_equal(
+            len(init_aware_ipv6), 1, "Multiple initiator IPv6 addresses")
+        asserts.assert_equal(
+            len(resp_aware_ipv6), 1, "Multiple responder IPv6 addresses")
+
+        for i in range(
+                init_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]):
+            # note: using get_ipv6_addr (ifconfig method) since want to verify
+            # that interfaces which do not have any NDPs on them do not have
+            # an IPv6 link-local address.
+            if_name = "%s%d" % (aconsts.AWARE_NDI_PREFIX, i)
+            init_ipv6 = autils.get_ipv6_addr(init_dut, if_name)
+            resp_ipv6 = autils.get_ipv6_addr(resp_dut, if_name)
+
+            asserts.assert_equal(
+                init_ipv6 is None, if_name not in init_aware_ifs,
+                "Initiator interface %s in unexpected state" % if_name)
+            asserts.assert_equal(
+                resp_ipv6 is None, if_name not in resp_aware_ifs,
+                "Responder interface %s in unexpected state" % if_name)
+
+        # release requests
+        for resp_req_key in resp_req_keys:
+            resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        for init_req_key in init_req_keys:
+            init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    @test_tracker_info(uuid="34cf12e8-5df6-49bd-b384-e9935d89a5b7")
+    @WifiBaseTest.wifi_test_wrap
+    def test_identical_network_from_both_sides(self):
+        """Validate that requesting two identical NDPs (Open) each being initiated
+    from a different side, results in the same/single NDP.
+
+    Verify that the interface and IPv6 address is the same for all networks.
+    """
+        dut1 = self.android_devices[0]
+        dut2 = self.android_devices[1]
+
+        id1, mac1 = autils.attach_with_identity(dut1)
+        id2, mac2 = autils.attach_with_identity(dut2)
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        # first NDP: DUT1 (Init) -> DUT2 (Resp)
+        req_a_resp = autils.request_network(
+            dut2,
+            dut2.droid.wifiAwareCreateNetworkSpecifierOob(
+                id2, aconsts.DATA_PATH_RESPONDER, mac1))
+
+        req_a_init = autils.request_network(
+            dut1,
+            dut1.droid.wifiAwareCreateNetworkSpecifierOob(
+                id1, aconsts.DATA_PATH_INITIATOR, mac2))
+
+        req_a_resp_event_nc = autils.wait_for_event_with_keys(
+            dut2, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_a_resp))
+        req_a_init_event_nc = autils.wait_for_event_with_keys(
+            dut1, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_a_init))
+
+        # validate no leak of information
+        asserts.assert_false(
+            cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in req_a_resp_event_nc[
+                "data"], "Network specifier leak!")
+        asserts.assert_false(
+            cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in req_a_init_event_nc[
+                "data"], "Network specifier leak!")
+
+        # note that Init <-> Resp since IPv6 are of peer's!
+        req_a_ipv6_init = req_a_resp_event_nc["data"][aconsts.NET_CAP_IPV6]
+        req_a_ipv6_resp = req_a_init_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+        req_a_resp_event_lp = autils.wait_for_event_with_keys(
+            dut2, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_a_resp))
+        req_a_init_event_lp = autils.wait_for_event_with_keys(
+            dut1, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_a_init))
+
+        req_a_if_resp = req_a_resp_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        req_a_if_init = req_a_init_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+        self.log.info("Interface names for A: I=%s, R=%s", req_a_if_init,
+                      req_a_if_resp)
+        self.log.info("Interface addresses (IPv6) for A: I=%s, R=%s",
+                      req_a_ipv6_init, req_a_ipv6_resp)
+
+        # second NDP: DUT2 (Init) -> DUT1 (Resp)
+        req_b_resp = autils.request_network(
+            dut1,
+            dut1.droid.wifiAwareCreateNetworkSpecifierOob(
+                id1, aconsts.DATA_PATH_RESPONDER, mac2))
+
+        req_b_init = autils.request_network(
+            dut2,
+            dut2.droid.wifiAwareCreateNetworkSpecifierOob(
+                id2, aconsts.DATA_PATH_INITIATOR, mac1))
+
+        req_b_resp_event_nc = autils.wait_for_event_with_keys(
+            dut1, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_b_resp))
+        req_b_init_event_nc = autils.wait_for_event_with_keys(
+            dut2, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_b_init))
+
+        # validate no leak of information
+        asserts.assert_false(
+            cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in req_b_resp_event_nc[
+                "data"], "Network specifier leak!")
+        asserts.assert_false(
+            cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in req_b_init_event_nc[
+                "data"], "Network specifier leak!")
+
+        # note that Init <-> Resp since IPv6 are of peer's!
+        req_b_ipv6_init = req_b_resp_event_nc["data"][aconsts.NET_CAP_IPV6]
+        req_b_ipv6_resp = req_b_init_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+        req_b_resp_event_lp = autils.wait_for_event_with_keys(
+            dut1, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_b_resp))
+        req_b_init_event_lp = autils.wait_for_event_with_keys(
+            dut2, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, req_b_init))
+
+        req_b_if_resp = req_b_resp_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        req_b_if_init = req_b_init_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+        self.log.info("Interface names for B: I=%s, R=%s", req_b_if_init,
+                      req_b_if_resp)
+        self.log.info("Interface addresses (IPv6) for B: I=%s, R=%s",
+                      req_b_ipv6_init, req_b_ipv6_resp)
+
+        # validate equality of NDPs (using interface names & ipv6)
+        asserts.assert_equal(req_a_if_init, req_b_if_resp,
+                             "DUT1 NDPs are on different interfaces")
+        asserts.assert_equal(req_a_if_resp, req_b_if_init,
+                             "DUT2 NDPs are on different interfaces")
+        asserts.assert_equal(req_a_ipv6_init, req_b_ipv6_resp,
+                             "DUT1 NDPs are using different IPv6 addresses")
+        asserts.assert_equal(req_a_ipv6_resp, req_b_ipv6_init,
+                             "DUT2 NDPs are using different IPv6 addresses")
+
+        # release requests
+        dut1.droid.connectivityUnregisterNetworkCallback(req_a_init)
+        dut1.droid.connectivityUnregisterNetworkCallback(req_b_resp)
+        dut2.droid.connectivityUnregisterNetworkCallback(req_a_resp)
+        dut2.droid.connectivityUnregisterNetworkCallback(req_b_init)
+
+    ########################################################################
+
+    def run_multiple_ndi(self, sec_configs, flip_init_resp=False):
+        """Validate that the device can create and use multiple NDIs.
+
+    The security configuration can be:
+    - None: open
+    - String: passphrase
+    - otherwise: PMK (byte array)
+
+    Args:
+      sec_configs: list of security configurations
+      flip_init_resp: if True the roles of Initiator and Responder are flipped
+                      between the 2 devices, otherwise same devices are always
+                      configured in the same role.
+    """
+        dut1 = self.android_devices[0]
+        dut2 = self.android_devices[1]
+
+        asserts.skip_if(
+            dut1.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] <
+            len(sec_configs)
+            or dut2.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] <
+            len(sec_configs), "DUTs do not support enough NDIs")
+
+        id1, mac1 = autils.attach_with_identity(dut1)
+        id2, mac2 = autils.attach_with_identity(dut2)
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        dut2_req_keys = []
+        dut1_req_keys = []
+        dut2_aware_ifs = []
+        dut1_aware_ifs = []
+        dut2_aware_ipv6s = []
+        dut1_aware_ipv6s = []
+
+        dut2_type = aconsts.DATA_PATH_RESPONDER
+        dut1_type = aconsts.DATA_PATH_INITIATOR
+        dut2_is_responder = True
+        for sec in sec_configs:
+            if dut2_is_responder:
+                # DUT2 (Responder): request network
+                dut2_req_key = autils.request_network(
+                    dut2,
+                    autils.get_network_specifier(dut2, id2, dut2_type, mac1,
+                                                 sec))
+                dut2_req_keys.append(dut2_req_key)
+
+                # DUT1 (Initiator): request network
+                dut1_req_key = autils.request_network(
+                    dut1,
+                    autils.get_network_specifier(dut1, id1, dut1_type, mac2,
+                                                 sec))
+                dut1_req_keys.append(dut1_req_key)
+            else:
+                # DUT1 (Responder): request network
+                dut1_req_key = autils.request_network(
+                    dut1,
+                    autils.get_network_specifier(dut1, id1, dut1_type, mac2,
+                                                 sec))
+                dut1_req_keys.append(dut1_req_key)
+
+                # DUT2 (Initiator): request network
+                dut2_req_key = autils.request_network(
+                    dut2,
+                    autils.get_network_specifier(dut2, id2, dut2_type, mac1,
+                                                 sec))
+                dut2_req_keys.append(dut2_req_key)
+
+            # Wait for network
+            dut1_net_event_nc = autils.wait_for_event_with_keys(
+                dut1, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, dut1_req_key))
+            dut2_net_event_nc = autils.wait_for_event_with_keys(
+                dut2, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, dut2_req_key))
+
+            # validate no leak of information
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in dut1_net_event_nc[
+                    "data"], "Network specifier leak!")
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in dut2_net_event_nc[
+                    "data"], "Network specifier leak!")
+
+            # Note: dut1 <--> dut2 IPv6's addresses since it is peer's info
+            dut2_aware_ipv6 = dut1_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+            dut1_aware_ipv6 = dut2_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+            dut1_net_event_lp = autils.wait_for_event_with_keys(
+                dut1, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, dut1_req_key))
+            dut2_net_event_lp = autils.wait_for_event_with_keys(
+                dut2, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, dut2_req_key))
+
+            dut2_aware_if = dut2_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+            dut1_aware_if = dut1_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+            dut2_aware_ifs.append(dut2_aware_if)
+            dut1_aware_ifs.append(dut1_aware_if)
+            dut2_aware_ipv6s.append(dut2_aware_ipv6)
+            dut1_aware_ipv6s.append(dut1_aware_ipv6)
+
+            if flip_init_resp:
+                if dut2_is_responder:
+                    dut2_type = aconsts.DATA_PATH_INITIATOR
+                    dut1_type = aconsts.DATA_PATH_RESPONDER
+                else:
+                    dut2_type = aconsts.DATA_PATH_RESPONDER
+                    dut1_type = aconsts.DATA_PATH_INITIATOR
+                dut2_is_responder = not dut2_is_responder
+
+        # check that we are using 2 NDIs & that they have unique IPv6 addresses
+        dut1_aware_ifs = list(set(dut1_aware_ifs))
+        dut2_aware_ifs = list(set(dut2_aware_ifs))
+        dut1_aware_ipv6s = list(set(dut1_aware_ipv6s))
+        dut2_aware_ipv6s = list(set(dut2_aware_ipv6s))
+
+        self.log.info("Interface names: DUT1=%s, DUT2=%s", dut1_aware_ifs,
+                      dut2_aware_ifs)
+        self.log.info("IPv6 addresses: DUT1=%s, DUT2=%s", dut1_aware_ipv6s,
+                      dut2_aware_ipv6s)
+        self.log.info("DUT1 requests: %s", dut1_req_keys)
+        self.log.info("DUT2 requests: %s", dut2_req_keys)
+
+        asserts.assert_equal(
+            len(dut1_aware_ifs), len(sec_configs), "Multiple DUT1 interfaces")
+        asserts.assert_equal(
+            len(dut2_aware_ifs), len(sec_configs), "Multiple DUT2 interfaces")
+        asserts.assert_equal(
+            len(dut1_aware_ipv6s), len(sec_configs),
+            "Multiple DUT1 IPv6 addresses")
+        asserts.assert_equal(
+            len(dut2_aware_ipv6s), len(sec_configs),
+            "Multiple DUT2 IPv6 addresses")
+
+        for i in range(len(sec_configs)):
+            # note: using get_ipv6_addr (ifconfig method) since want to verify
+            # that the system information is the same as the reported information
+            if_name = "%s%d" % (aconsts.AWARE_NDI_PREFIX, i)
+            dut1_ipv6 = autils.get_ipv6_addr(dut1, if_name)
+            dut2_ipv6 = autils.get_ipv6_addr(dut2, if_name)
+
+            asserts.assert_equal(
+                dut1_ipv6 is None, if_name not in dut1_aware_ifs,
+                "DUT1 interface %s in unexpected state" % if_name)
+            asserts.assert_equal(
+                dut2_ipv6 is None, if_name not in dut2_aware_ifs,
+                "DUT2 interface %s in unexpected state" % if_name)
+
+        # release requests
+        for dut2_req_key in dut2_req_keys:
+            dut2.droid.connectivityUnregisterNetworkCallback(dut2_req_key)
+        for dut1_req_key in dut1_req_keys:
+            dut1.droid.connectivityUnregisterNetworkCallback(dut1_req_key)
+
+    @test_tracker_info(uuid="2d728163-11cc-46ba-a973-c8e1e71397fc")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_open_passphrase(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (one open, one using passphrase). The result should use two
+    different NDIs"""
+        self.run_multiple_ndi([None, self.PASSPHRASE])
+
+    @test_tracker_info(uuid="5f2c32aa-20b2-41f0-8b1e-d0b68df73ada")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_open_pmk(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (one open, one using pmk). The result should use two
+    different NDIs"""
+        self.run_multiple_ndi([None, self.PMK])
+
+    @test_tracker_info(uuid="34467659-bcfb-40cd-ba25-7e50560fca63")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_passphrase_pmk(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (one using passphrase, one using pmk). The result should use
+    two different NDIs"""
+        self.run_multiple_ndi([self.PASSPHRASE, self.PMK])
+
+    @test_tracker_info(uuid="d9194ce6-45b6-41b1-9cc8-ada79968966d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_passphrases(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (using different passphrases). The result should use two
+    different NDIs"""
+        self.run_multiple_ndi([self.PASSPHRASE, self.PASSPHRASE2])
+
+    @test_tracker_info(uuid="879df795-62d2-40d4-a862-bd46d8f7e67f")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_pmks(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (using different PMKS). The result should use two different
+    NDIs"""
+        self.run_multiple_ndi([self.PMK, self.PMK2])
+
+    @test_tracker_info(uuid="397d380a-8e41-466e-9ccb-cf8f413d83ba")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_open_passphrase_flip(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (one open, one using passphrase). The result should use two
+    different NDIs.
+
+    Flip Initiator and Responder roles.
+    """
+        self.run_multiple_ndi([None, self.PASSPHRASE], flip_init_resp=True)
+
+    @test_tracker_info(uuid="b3a4300b-1514-4cb8-a814-9c2baa449700")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_open_pmk_flip(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (one open, one using pmk). The result should use two
+    different NDIs
+
+    Flip Initiator and Responder roles.
+    """
+        self.run_multiple_ndi([None, self.PMK], flip_init_resp=True)
+
+    @test_tracker_info(uuid="0bfea9e4-e57d-417f-8db4-245741e9bbd5")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_passphrase_pmk_flip(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (one using passphrase, one using pmk). The result should use
+    two different NDIs
+
+    Flip Initiator and Responder roles.
+    """
+        self.run_multiple_ndi([self.PASSPHRASE, self.PMK], flip_init_resp=True)
+
+    @test_tracker_info(uuid="74023483-5417-431b-a362-991ad4a03ab8")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_passphrases_flip(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (using different passphrases). The result should use two
+    different NDIs
+
+    Flip Initiator and Responder roles.
+    """
+        self.run_multiple_ndi(
+            [self.PASSPHRASE, self.PASSPHRASE2], flip_init_resp=True)
+
+    @test_tracker_info(uuid="873b2d91-28a1-403f-ae9c-d756bb2f59ee")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndi_pmks_flip(self):
+        """Verify that between 2 DUTs can create 2 NDPs with different security
+    configuration (using different PMKS). The result should use two different
+    NDIs
+
+    Flip Initiator and Responder roles.
+    """
+        self.run_multiple_ndi([self.PMK, self.PMK2], flip_init_resp=True)
+
+    #######################################
+
+    @test_tracker_info(uuid="2f10a9df-7fbd-490d-a238-3523f47ab54c")
+    @WifiBaseTest.wifi_test_wrap
+    def test_ib_responder_any_usage(self):
+        """Verify that configuring an in-band (Aware discovery) Responder to receive
+    an NDP request from any peer is not permitted by current API level. Override
+    API check to validate that possible (i.e. that failure at current API level
+    is due to an API check and not some underlying failure).
+    """
+
+        # configure all devices to override API check and allow a Responder from ANY
+        for ad in self.android_devices:
+            autils.configure_ndp_allow_any_override(ad, True)
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False)
+
+        # configure all devices to respect API check - i.e. disallow a Responder
+        # from ANY
+        for ad in self.android_devices:
+            autils.configure_ndp_allow_any_override(ad, False)
+        self.run_ib_data_path_test(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False,
+            expect_failure=True)
+
+    @test_tracker_info(uuid="5889cd41-0a72-4b7b-ab82-5b9168b9b5b8")
+    @WifiBaseTest.wifi_test_wrap
+    def test_oob_responder_any_usage(self):
+        """Verify that configuring an out-of-band (Aware discovery) Responder to
+    receive an NDP request from any peer is not permitted by current API level.
+    Override API check to validate that possible (i.e. that failure at current
+    API level is due to an API check and not some underlying failure).
+    """
+
+        # configure all devices to override API check and allow a Responder from ANY
+        for ad in self.android_devices:
+            autils.configure_ndp_allow_any_override(ad, True)
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_OPEN, use_peer_id=False)
+
+        # configure all devices to respect API check - i.e. disallow a Responder
+        # from ANY
+        for ad in self.android_devices:
+            autils.configure_ndp_allow_any_override(ad, False)
+        self.run_oob_data_path_test(
+            encr_type=self.ENCR_TYPE_OPEN,
+            use_peer_id=False,
+            expect_failure=True)
+
+    #######################################
+
+    def run_multiple_regulatory_domains(self, use_ib, init_domain,
+                                        resp_domain):
+        """Verify that a data-path setup with two conflicting regulatory domains
+    works (the result should be run in Channel 6 - but that is not tested).
+
+    Args:
+      use_ib: True to use in-band discovery, False to use out-of-band discovery.
+      init_domain: The regulatory domain of the Initiator/Subscriber.
+      resp_domain: The regulator domain of the Responder/Publisher.
+    """
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        wutils.set_wifi_country_code(init_dut, init_domain)
+        wutils.set_wifi_country_code(resp_dut, resp_domain)
+
+        if use_ib:
+            (resp_req_key, init_req_key, resp_aware_if, init_aware_if,
+             resp_ipv6, init_ipv6) = autils.create_ib_ndp(
+                 resp_dut, init_dut,
+                 autils.create_discovery_config(
+                     "GoogleTestXyz", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                 autils.create_discovery_config(
+                     "GoogleTestXyz", aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                 self.device_startup_offset)
+        else:
+            (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+             init_ipv6, resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+
+        self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                      resp_aware_if)
+        self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                      resp_ipv6)
+
+        # clean-up
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    @test_tracker_info(uuid="eff53739-35c5-47a6-81f0-d70b51d89c3b")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_regulator_domains_ib_us_jp(self):
+        """Verify data-path setup across multiple regulator domains.
+
+    - Uses in-band discovery
+    - Subscriber=US, Publisher=JP
+    """
+        self.run_multiple_regulatory_domains(
+            use_ib=True,
+            init_domain=wutils.WifiEnums.CountryCode.US,
+            resp_domain=wutils.WifiEnums.CountryCode.JAPAN)
+
+    @test_tracker_info(uuid="19af47cc-3204-40ef-b50f-14cf7b89cf4a")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_regulator_domains_ib_jp_us(self):
+        """Verify data-path setup across multiple regulator domains.
+
+    - Uses in-band discovery
+    - Subscriber=JP, Publisher=US
+    """
+        self.run_multiple_regulatory_domains(
+            use_ib=True,
+            init_domain=wutils.WifiEnums.CountryCode.JAPAN,
+            resp_domain=wutils.WifiEnums.CountryCode.US)
+
+    @test_tracker_info(uuid="65285ab3-977f-4dbd-b663-d5a02f4fc663")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_regulator_domains_oob_us_jp(self):
+        """Verify data-path setup across multiple regulator domains.
+
+    - Uses out-f-band discovery
+    - Initiator=US, Responder=JP
+    """
+        self.run_multiple_regulatory_domains(
+            use_ib=False,
+            init_domain=wutils.WifiEnums.CountryCode.US,
+            resp_domain=wutils.WifiEnums.CountryCode.JAPAN)
+
+    @test_tracker_info(uuid="8a417e24-aaf6-44b9-a089-a07c3ba8d954")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_regulator_domains_oob_jp_us(self):
+        """Verify data-path setup across multiple regulator domains.
+
+    - Uses out-of-band discovery
+    - Initiator=JP, Responder=US
+    """
+        self.run_multiple_regulatory_domains(
+            use_ib=False,
+            init_domain=wutils.WifiEnums.CountryCode.JAPAN,
+            resp_domain=wutils.WifiEnums.CountryCode.US)
+
+    ########################################################################
+
+    def run_mix_ib_oob(self, same_request, ib_first, inits_on_same_dut):
+        """Validate that multiple network requests issued using both in-band and
+    out-of-band discovery behave as expected.
+
+    The same_request parameter controls whether identical single NDP is
+    expected, if True, or whether multiple NDPs on different NDIs are expected,
+    if False.
+
+    Args:
+      same_request: Issue canonically identical requests (same NMI peer, same
+                    passphrase) if True, if False use different passphrases.
+      ib_first: If True then the in-band network is requested first, otherwise
+                (if False) then the out-of-band network is requested first.
+      inits_on_same_dut: If True then the Initiators are run on the same device,
+                         otherwise (if False) then the Initiators are run on
+                         different devices. Note that Subscribe == Initiator.
+    """
+        if not same_request:
+            asserts.skip_if(
+                self.android_devices[0]
+                .aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] < 2
+                or self.android_devices[1]
+                .aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] < 2,
+                "DUTs do not support enough NDIs")
+
+        (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+         peer_id_on_pub_null) = self.set_up_discovery(
+             aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE,
+             False)
+
+        p_id2, p_mac = autils.attach_with_identity(p_dut)
+        s_id2, s_mac = autils.attach_with_identity(s_dut)
+
+        if inits_on_same_dut:
+            resp_dut = p_dut
+            resp_id = p_id2
+            resp_mac = p_mac
+
+            init_dut = s_dut
+            init_id = s_id2
+            init_mac = s_mac
+        else:
+            resp_dut = s_dut
+            resp_id = s_id2
+            resp_mac = s_mac
+
+            init_dut = p_dut
+            init_id = p_id2
+            init_mac = p_mac
+
+        passphrase = None if same_request else self.PASSPHRASE
+
+        if ib_first:
+            # request in-band network (to completion)
+            p_req_key = self.request_network(
+                p_dut,
+                p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, None))
+            s_req_key = self.request_network(
+                s_dut,
+                s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                    s_disc_id, peer_id_on_sub))
+
+            # Publisher & Subscriber: wait for network formation
+            p_net_event_nc = autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            s_net_event_nc = autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+            p_net_event_lp = autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            s_net_event_lp = autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+            # validate no leak of information
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in p_net_event_nc[
+                    "data"], "Network specifier leak!")
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in s_net_event_nc[
+                    "data"], "Network specifier leak!")
+
+        # request out-of-band network
+        resp_req_key = autils.request_network(
+            resp_dut,
+            resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, passphrase))
+        init_req_key = autils.request_network(
+            init_dut,
+            init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, passphrase))
+
+        resp_net_event_nc = autils.wait_for_event_with_keys(
+            resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+        init_net_event_nc = autils.wait_for_event_with_keys(
+            init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+        resp_net_event_lp = autils.wait_for_event_with_keys(
+            resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+        init_net_event_lp = autils.wait_for_event_with_keys(
+            init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+
+        # validate no leak of information
+        asserts.assert_false(
+            cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in resp_net_event_nc[
+                "data"], "Network specifier leak!")
+        asserts.assert_false(
+            cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in init_net_event_nc[
+                "data"], "Network specifier leak!")
+
+        if not ib_first:
+            # request in-band network (to completion)
+            p_req_key = self.request_network(
+                p_dut,
+                p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, None))
+            s_req_key = self.request_network(
+                s_dut,
+                s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                    s_disc_id, peer_id_on_sub))
+
+            # Publisher & Subscriber: wait for network formation
+            p_net_event_nc = autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            s_net_event_nc = autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+            p_net_event_lp = autils.wait_for_event_with_keys(
+                p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+            s_net_event_lp = autils.wait_for_event_with_keys(
+                s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+            # validate no leak of information
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in p_net_event_nc[
+                    "data"], "Network specifier leak!")
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in s_net_event_nc[
+                    "data"], "Network specifier leak!")
+
+        # note that Init <-> Resp & Pub <--> Sub since IPv6 are of peer's!
+        init_ipv6 = resp_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+        resp_ipv6 = init_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+        pub_ipv6 = s_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+        sub_ipv6 = p_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+        # extract net info
+        pub_interface = p_net_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        sub_interface = s_net_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        resp_interface = resp_net_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        init_interface = init_net_event_lp["data"][
+            cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+        self.log.info("Interface names: Pub=%s, Sub=%s, Resp=%s, Init=%s",
+                      pub_interface, sub_interface, resp_interface,
+                      init_interface)
+        self.log.info(
+            "Interface addresses (IPv6): Pub=%s, Sub=%s, Resp=%s, Init=%s",
+            pub_ipv6, sub_ipv6, resp_ipv6, init_ipv6)
+
+        # validate NDP/NDI conditions (using interface names & ipv6)
+        if same_request:
+            asserts.assert_equal(
+                pub_interface, resp_interface if inits_on_same_dut else
+                init_interface, "NDP interfaces don't match on Pub/other")
+            asserts.assert_equal(
+                sub_interface, init_interface if inits_on_same_dut else
+                resp_interface, "NDP interfaces don't match on Sub/other")
+
+            asserts.assert_equal(pub_ipv6, resp_ipv6
+                                 if inits_on_same_dut else init_ipv6,
+                                 "NDP IPv6 don't match on Pub/other")
+            asserts.assert_equal(sub_ipv6, init_ipv6
+                                 if inits_on_same_dut else resp_ipv6,
+                                 "NDP IPv6 don't match on Sub/other")
+        else:
+            asserts.assert_false(
+                pub_interface == (resp_interface
+                                  if inits_on_same_dut else init_interface),
+                "NDP interfaces match on Pub/other")
+            asserts.assert_false(
+                sub_interface == (init_interface
+                                  if inits_on_same_dut else resp_interface),
+                "NDP interfaces match on Sub/other")
+
+            asserts.assert_false(
+                pub_ipv6 == (resp_ipv6 if inits_on_same_dut else init_ipv6),
+                "NDP IPv6 match on Pub/other")
+            asserts.assert_false(
+                sub_ipv6 == (init_ipv6 if inits_on_same_dut else resp_ipv6),
+                "NDP IPv6 match on Sub/other")
+
+        # release requests
+        p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    @test_tracker_info(uuid="d8a0839d-4ba0-43f2-af93-3cf1382f9f16")
+    @WifiBaseTest.wifi_test_wrap
+    def test_identical_ndps_mix_ib_oob_ib_first_same_polarity(self):
+        """Validate that a single NDP is created for multiple identical requests
+    which are issued through either in-band (ib) or out-of-band (oob) APIs.
+
+    The in-band request is issued first. Both Initiators (Sub == Initiator) are
+    run on the same device.
+    """
+        self.run_mix_ib_oob(
+            same_request=True, ib_first=True, inits_on_same_dut=True)
+
+    @test_tracker_info(uuid="70bbb811-0bed-4a19-96b3-f2446e777c8a")
+    @WifiBaseTest.wifi_test_wrap
+    def test_identical_ndps_mix_ib_oob_oob_first_same_polarity(self):
+        """Validate that a single NDP is created for multiple identical requests
+    which are issued through either in-band (ib) or out-of-band (oob) APIs.
+
+    The out-of-band request is issued first. Both Initiators (Sub == Initiator)
+    are run on the same device.
+    """
+        self.run_mix_ib_oob(
+            same_request=True, ib_first=False, inits_on_same_dut=True)
+
+    @test_tracker_info(uuid="d9796da5-f96a-4a51-be0f-89d6f5bfe3ad")
+    @WifiBaseTest.wifi_test_wrap
+    def test_identical_ndps_mix_ib_oob_ib_first_diff_polarity(self):
+        """Validate that a single NDP is created for multiple identical requests
+    which are issued through either in-band (ib) or out-of-band (oob) APIs.
+
+    The in-band request is issued first. Initiators (Sub == Initiator) are
+    run on different devices.
+    """
+        self.run_mix_ib_oob(
+            same_request=True, ib_first=True, inits_on_same_dut=False)
+
+    @test_tracker_info(uuid="48b9005b-7851-4222-b41c-1fcbefbc704d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_identical_ndps_mix_ib_oob_oob_first_diff_polarity(self):
+        """Validate that a single NDP is created for multiple identical requests
+    which are issued through either in-band (ib) or out-of-band (oob) APIs.
+
+    The out-of-band request is issued first. Initiators (Sub == Initiator) are
+    run on different devices.
+    """
+        self.run_mix_ib_oob(
+            same_request=True, ib_first=False, inits_on_same_dut=False)
+
+    @test_tracker_info(uuid="51f9581e-c5ee-48a7-84d2-adff4876c3d7")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndis_mix_ib_oob_ib_first_same_polarity(self):
+        """Validate that multiple NDIs are created for NDPs which are requested with
+    different security configurations. Use a mix of in-band and out-of-band APIs
+    to request the different NDPs.
+
+    The in-band request is issued first. Initiators (Sub == Initiator) are
+    run on the same device.
+    """
+        self.run_mix_ib_oob(
+            same_request=False, ib_first=True, inits_on_same_dut=True)
+
+    @test_tracker_info(uuid="b1e3070e-4d38-4b31-862d-39b82e0f2853")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndis_mix_ib_oob_oob_first_same_polarity(self):
+        """Validate that multiple NDIs are created for NDPs which are requested with
+    different security configurations. Use a mix of in-band and out-of-band APIs
+    to request the different NDPs.
+
+    The out-of-band request is issued first. Initiators (Sub == Initiator) are
+    run on the same device.
+    """
+        self.run_mix_ib_oob(
+            same_request=False, ib_first=False, inits_on_same_dut=True)
+
+    @test_tracker_info(uuid="b1e3070e-4d38-4b31-862d-39b82e0f2853")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndis_mix_ib_oob_ib_first_diff_polarity(self):
+        """Validate that multiple NDIs are created for NDPs which are requested with
+    different security configurations. Use a mix of in-band and out-of-band APIs
+    to request the different NDPs.
+
+    The in-band request is issued first. Initiators (Sub == Initiator) are
+    run on different devices.
+    """
+        self.run_mix_ib_oob(
+            same_request=False, ib_first=True, inits_on_same_dut=False)
+
+    @test_tracker_info(uuid="596caadf-028e-494b-bbce-8304ccec2cbb")
+    @WifiBaseTest.wifi_test_wrap
+    def test_multiple_ndis_mix_ib_oob_oob_first_diff_polarity(self):
+        """Validate that multiple NDIs are created for NDPs which are requested with
+    different security configurations. Use a mix of in-band and out-of-band APIs
+    to request the different NDPs.
+
+    The out-of-band request is issued first. Initiators (Sub == Initiator) are
+    run on different devices.
+    """
+        self.run_mix_ib_oob(
+            same_request=False, ib_first=False, inits_on_same_dut=False)
+
+    ########################################################################
+
+    @test_tracker_info(uuid="5ec10bf9-bfda-4093-8344-7ccc7764737e")
+    def test_ndp_loop(self):
+        """Validate that can create a loop (chain) of N NDPs between N devices,
+    where N >= 3, e.g.
+
+    A - B
+    B - C
+    C - A
+
+    The NDPs are all OPEN (no encryption).
+    """
+        asserts.skip_if(
+            len(self.android_devices) < 3,
+            'A minimum of 3 devices is needed to run the test, have %d' % len(
+                self.android_devices))
+
+        duts = self.android_devices
+        loop_len = len(duts)
+        ids = []
+        macs = []
+        reqs = []
+        ifs = []
+        ipv6s = []
+
+        for i in range(loop_len):
+            duts[i].pretty_name = chr(ord("A") + i)
+            reqs.append([])
+            ifs.append([])
+            ipv6s.append([])
+
+        # start-up 3 devices (attach w/ identity)
+        for i in range(loop_len):
+            ids.append(duts[i].droid.wifiAwareAttach(True))
+            autils.wait_for_event(duts[i], aconsts.EVENT_CB_ON_ATTACHED)
+            ident_event = autils.wait_for_event(
+                duts[i], aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+            macs.append(ident_event['data']['mac'])
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        # create the N NDPs: i to (i+1) % N
+        for i in range(loop_len):
+            peer_device = (i + 1) % loop_len
+
+            (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+             init_ipv6, resp_ipv6) = autils.create_oob_ndp_on_sessions(
+                 duts[i], duts[peer_device], ids[i], macs[i], ids[peer_device],
+                 macs[peer_device])
+
+            reqs[i].append(init_req_key)
+            reqs[peer_device].append(resp_req_key)
+            ifs[i].append(init_aware_if)
+            ifs[peer_device].append(resp_aware_if)
+            ipv6s[i].append(init_ipv6)
+            ipv6s[peer_device].append(resp_ipv6)
+
+        # clean-up
+        for i in range(loop_len):
+            for req in reqs[i]:
+                duts[i].droid.connectivityUnregisterNetworkCallback(req)
+
+        # info
+        self.log.info("MACs: %s", macs)
+        self.log.info("Interface names: %s", ifs)
+        self.log.info("IPv6 addresses: %s", ipv6s)
+        asserts.explicit_pass(
+            "NDP loop test", extras={
+                "macs": macs,
+                "ifs": ifs,
+                "ipv6s": ipv6s
+            })
diff --git a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
new file mode 100644
index 0000000..31255af
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -0,0 +1,1263 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import string
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class DiscoveryTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware discovery."""
+
+    # configuration parameters used by tests
+    PAYLOAD_SIZE_MIN = 0
+    PAYLOAD_SIZE_TYPICAL = 1
+    PAYLOAD_SIZE_MAX = 2
+
+    # message strings
+    query_msg = "How are you doing? 你好嗎?"
+    response_msg = "Doing ok - thanks! 做的不錯 - 謝謝!"
+
+    # message re-transmit counter (increases reliability in open-environment)
+    # Note: reliability of message transmission is tested elsewhere
+    msg_retx_count = 5  # hard-coded max value, internal API
+
+    def create_base_config(self, caps, is_publish, ptype, stype, payload_size,
+                           ttl, term_ind_on, null_match):
+        """Create a base configuration based on input parameters.
+
+    Args:
+      caps: device capability dictionary
+      is_publish: True if a publish config, else False
+      ptype: unsolicited or solicited (used if is_publish is True)
+      stype: passive or active (used if is_publish is False)
+      payload_size: min, typical, max (PAYLOAD_SIZE_xx)
+      ttl: time-to-live configuration (0 - forever)
+      term_ind_on: is termination indication enabled
+      null_match: null-out the middle match filter
+    Returns:
+      publish discovery configuration object.
+    """
+        config = {}
+        if is_publish:
+            config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = ptype
+        else:
+            config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = stype
+        config[aconsts.DISCOVERY_KEY_TTL] = ttl
+        config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_ind_on
+        if payload_size == self.PAYLOAD_SIZE_MIN:
+            config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "a"
+            config[aconsts.DISCOVERY_KEY_SSI] = None
+            config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = []
+        elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
+            config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX"
+            if is_publish:
+                config[aconsts.DISCOVERY_KEY_SSI] = string.ascii_letters
+            else:
+                config[aconsts.
+                       DISCOVERY_KEY_SSI] = string.ascii_letters[::
+                                                                 -1]  # reverse
+            config[
+                aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
+                    [(10).to_bytes(1, byteorder="big"), "hello there string"
+                     if not null_match else None,
+                     bytes(range(40))])
+        else:  # PAYLOAD_SIZE_MAX
+            config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "VeryLong" + "X" * (
+                caps[aconsts.CAP_MAX_SERVICE_NAME_LEN] - 8)
+            config[aconsts.DISCOVERY_KEY_SSI] = (
+                "P" if is_publish else
+                "S") * caps[aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN]
+            mf = autils.construct_max_match_filter(
+                caps[aconsts.CAP_MAX_MATCH_FILTER_LEN])
+            if null_match:
+                mf[2] = None
+            config[aconsts.
+                   DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(mf)
+
+        return config
+
+    def create_publish_config(self, caps, ptype, payload_size, ttl,
+                              term_ind_on, null_match):
+        """Create a publish configuration based on input parameters.
+
+    Args:
+      caps: device capability dictionary
+      ptype: unsolicited or solicited
+      payload_size: min, typical, max (PAYLOAD_SIZE_xx)
+      ttl: time-to-live configuration (0 - forever)
+      term_ind_on: is termination indication enabled
+      null_match: null-out the middle match filter
+    Returns:
+      publish discovery configuration object.
+    """
+        return self.create_base_config(caps, True, ptype, None, payload_size,
+                                       ttl, term_ind_on, null_match)
+
+    def create_subscribe_config(self, caps, stype, payload_size, ttl,
+                                term_ind_on, null_match):
+        """Create a subscribe configuration based on input parameters.
+
+    Args:
+      caps: device capability dictionary
+      stype: passive or active
+      payload_size: min, typical, max (PAYLOAD_SIZE_xx)
+      ttl: time-to-live configuration (0 - forever)
+      term_ind_on: is termination indication enabled
+      null_match: null-out the middle match filter
+    Returns:
+      subscribe discovery configuration object.
+    """
+        return self.create_base_config(caps, False, None, stype, payload_size,
+                                       ttl, term_ind_on, null_match)
+
+    def positive_discovery_test_utility(self, ptype, stype, payload_size):
+        """Utility which runs a positive discovery test:
+    - Discovery (publish/subscribe) with TTL=0 (non-self-terminating)
+    - Exchange messages
+    - Update publish/subscribe
+    - Terminate
+
+    Args:
+      ptype: Publish discovery type
+      stype: Subscribe discovery type
+      payload_size: One of PAYLOAD_SIZE_* constants - MIN, TYPICAL, MAX
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: start publish and wait for confirmation
+        p_config = self.create_publish_config(
+            p_dut.aware_capabilities,
+            ptype,
+            payload_size,
+            ttl=0,
+            term_ind_on=False,
+            null_match=False)
+        p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Subscriber: start subscribe and wait for confirmation
+        s_config = self.create_subscribe_config(
+            s_dut.aware_capabilities,
+            stype,
+            payload_size,
+            ttl=0,
+            term_ind_on=False,
+            null_match=True)
+        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # Subscriber: wait for service discovery
+        discovery_event = autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        peer_id_on_sub = discovery_event["data"][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Subscriber: validate contents of discovery:
+        # - SSI: publisher's
+        # - Match filter: UNSOLICITED - publisher, SOLICITED - subscriber
+        autils.assert_equal_strings(
+            bytes(discovery_event["data"][
+                aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode("utf-8"),
+            p_config[aconsts.DISCOVERY_KEY_SSI],
+            "Discovery mismatch: service specific info (SSI)")
+        asserts.assert_equal(
+            autils.decode_list(discovery_event["data"][
+                aconsts.SESSION_CB_KEY_MATCH_FILTER_LIST]),
+            autils.decode_list(
+                p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]
+                if ptype == aconsts.PUBLISH_TYPE_UNSOLICITED else s_config[
+                    aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]),
+            "Discovery mismatch: match filter")
+
+        # Subscriber: send message to peer (Publisher)
+        s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                         self.get_next_msg_id(),
+                                         self.query_msg, self.msg_retx_count)
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+        # Publisher: wait for received message
+        pub_rx_msg_event = autils.wait_for_event(
+            p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+        peer_id_on_pub = pub_rx_msg_event["data"][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Publisher: validate contents of message
+        asserts.assert_equal(
+            pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+            self.query_msg, "Subscriber -> Publisher message corrupted")
+
+        # Publisher: send message to peer (Subscriber)
+        p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
+                                         self.get_next_msg_id(),
+                                         self.response_msg,
+                                         self.msg_retx_count)
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+        # Subscriber: wait for received message
+        sub_rx_msg_event = autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+
+        # Subscriber: validate contents of message
+        asserts.assert_equal(
+            sub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_PEER_ID],
+            peer_id_on_sub,
+            "Subscriber received message from different peer ID then discovery!?"
+        )
+        autils.assert_equal_strings(
+            sub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+            self.response_msg, "Publisher -> Subscriber message corrupted")
+
+        # Subscriber: validate that we're not getting another Service Discovery
+        autils.fail_on_event(s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+        # Publisher: update publish and wait for confirmation
+        p_config[aconsts.DISCOVERY_KEY_SSI] = "something else"
+        p_dut.droid.wifiAwareUpdatePublish(p_disc_id, p_config)
+        autils.wait_for_event(p_dut,
+                              aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)
+
+        # Subscriber: expect a new service discovery
+        discovery_event = autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+        # Subscriber: validate contents of discovery
+        autils.assert_equal_strings(
+            bytes(discovery_event["data"][
+                aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode("utf-8"),
+            p_config[aconsts.DISCOVERY_KEY_SSI],
+            "Discovery mismatch (after pub update): service specific info (SSI)"
+        )
+        asserts.assert_equal(
+            autils.decode_list(discovery_event["data"][
+                aconsts.SESSION_CB_KEY_MATCH_FILTER_LIST]),
+            autils.decode_list(
+                p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]
+                if ptype == aconsts.PUBLISH_TYPE_UNSOLICITED else s_config[
+                    aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]),
+            "Discovery mismatch: match filter")
+        asserts.assert_equal(
+            peer_id_on_sub,
+            discovery_event["data"][aconsts.SESSION_CB_KEY_PEER_ID],
+            "Peer ID changed when publish was updated!?")
+
+        # Subscribe: update subscribe and wait for confirmation
+        s_config = self.create_subscribe_config(
+            s_dut.aware_capabilities,
+            stype,
+            payload_size,
+            ttl=0,
+            term_ind_on=False,
+            null_match=False)
+        s_dut.droid.wifiAwareUpdateSubscribe(s_disc_id, s_config)
+        autils.wait_for_event(s_dut,
+                              aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)
+
+        # Publisher+Subscriber: Terminate sessions
+        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+        # sleep for timeout period and then verify all 'fail_on_event' together
+        time.sleep(autils.EVENT_TIMEOUT)
+
+        # verify that there were no other events
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+        # verify that forbidden callbacks aren't called
+        autils.validate_forbidden_callbacks(p_dut, {aconsts.CB_EV_MATCH: 0})
+
+    def verify_discovery_session_term(self, dut, disc_id, config, is_publish,
+                                      term_ind_on):
+        """Utility to verify that the specified discovery session has terminated (by
+    waiting for the TTL and then attempting to reconfigure).
+
+    Args:
+      dut: device under test
+      disc_id: discovery id for the existing session
+      config: configuration of the existing session
+      is_publish: True if the configuration was publish, False if subscribe
+      term_ind_on: True if a termination indication is expected, False otherwise
+    """
+        # Wait for session termination
+        if term_ind_on:
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_TERMINATED,
+                                      disc_id))
+        else:
+            # can't defer wait to end since in any case have to wait for session to
+            # expire
+            autils.fail_on_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_TERMINATED,
+                                      disc_id))
+
+        # Validate that session expired by trying to configure it (expect failure)
+        config[aconsts.DISCOVERY_KEY_SSI] = "something else"
+        if is_publish:
+            dut.droid.wifiAwareUpdatePublish(disc_id, config)
+        else:
+            dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
+
+        # The response to update discovery session is:
+        # term_ind_on=True: session was cleaned-up so won't get an explicit failure, but won't get a
+        #                   success either. Can check for no SESSION_CB_ON_SESSION_CONFIG_UPDATED but
+        #                   will defer to the end of the test (no events on queue).
+        # term_ind_on=False: session was not cleaned-up (yet). So expect
+        #                    SESSION_CB_ON_SESSION_CONFIG_FAILED.
+        if not term_ind_on:
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(
+                    aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED, disc_id))
+
+    def positive_ttl_test_utility(self, is_publish, ptype, stype, term_ind_on):
+        """Utility which runs a positive discovery session TTL configuration test
+
+    Iteration 1: Verify session started with TTL
+    Iteration 2: Verify session started without TTL and reconfigured with TTL
+    Iteration 3: Verify session started with (long) TTL and reconfigured with
+                 (short) TTL
+
+    Args:
+      is_publish: True if testing publish, False if testing subscribe
+      ptype: Publish discovery type (used if is_publish is True)
+      stype: Subscribe discovery type (used if is_publish is False)
+      term_ind_on: Configuration of termination indication
+    """
+        SHORT_TTL = 5  # 5 seconds
+        LONG_TTL = 100  # 100 seconds
+        dut = self.android_devices[0]
+
+        # Attach and wait for confirmation
+        id = dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Iteration 1: Start discovery session with TTL
+        config = self.create_base_config(
+            dut.aware_capabilities, is_publish, ptype, stype,
+            self.PAYLOAD_SIZE_TYPICAL, SHORT_TTL, term_ind_on, False)
+        if is_publish:
+            disc_id = dut.droid.wifiAwarePublish(id, config, True)
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                      disc_id))
+        else:
+            disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                      disc_id))
+
+        # Wait for session termination & verify
+        self.verify_discovery_session_term(dut, disc_id, config, is_publish,
+                                           term_ind_on)
+
+        # Iteration 2: Start a discovery session without TTL
+        config = self.create_base_config(
+            dut.aware_capabilities, is_publish, ptype, stype,
+            self.PAYLOAD_SIZE_TYPICAL, 0, term_ind_on, False)
+        if is_publish:
+            disc_id = dut.droid.wifiAwarePublish(id, config, True)
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                      disc_id))
+        else:
+            disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                      disc_id))
+
+        # Update with a TTL
+        config = self.create_base_config(
+            dut.aware_capabilities, is_publish, ptype, stype,
+            self.PAYLOAD_SIZE_TYPICAL, SHORT_TTL, term_ind_on, False)
+        if is_publish:
+            dut.droid.wifiAwareUpdatePublish(disc_id, config)
+        else:
+            dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
+        autils.wait_for_event(
+            dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
+                                  disc_id))
+
+        # Wait for session termination & verify
+        self.verify_discovery_session_term(dut, disc_id, config, is_publish,
+                                           term_ind_on)
+
+        # Iteration 3: Start a discovery session with (long) TTL
+        config = self.create_base_config(
+            dut.aware_capabilities, is_publish, ptype, stype,
+            self.PAYLOAD_SIZE_TYPICAL, LONG_TTL, term_ind_on, False)
+        if is_publish:
+            disc_id = dut.droid.wifiAwarePublish(id, config, True)
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                      disc_id))
+        else:
+            disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
+            autils.wait_for_event(
+                dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                      disc_id))
+
+        # Update with a TTL
+        config = self.create_base_config(
+            dut.aware_capabilities, is_publish, ptype, stype,
+            self.PAYLOAD_SIZE_TYPICAL, SHORT_TTL, term_ind_on, False)
+        if is_publish:
+            dut.droid.wifiAwareUpdatePublish(disc_id, config)
+        else:
+            dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
+        autils.wait_for_event(
+            dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
+                                  disc_id))
+
+        # Wait for session termination & verify
+        self.verify_discovery_session_term(dut, disc_id, config, is_publish,
+                                           term_ind_on)
+
+        # verify that there were no other events
+        autils.verify_no_more_events(dut)
+
+        # verify that forbidden callbacks aren't called
+        if not term_ind_on:
+            autils.validate_forbidden_callbacks(
+                dut, {
+                    aconsts.CB_EV_PUBLISH_TERMINATED: 0,
+                    aconsts.CB_EV_SUBSCRIBE_TERMINATED: 0
+                })
+
+    def discovery_mismatch_test_utility(self,
+                                        is_expected_to_pass,
+                                        p_type,
+                                        s_type,
+                                        p_service_name=None,
+                                        s_service_name=None,
+                                        p_mf_1=None,
+                                        s_mf_1=None):
+        """Utility which runs the negative discovery test for mismatched service
+    configs.
+
+    Args:
+      is_expected_to_pass: True if positive test, False if negative
+      p_type: Publish discovery type
+      s_type: Subscribe discovery type
+      p_service_name: Publish service name (or None to leave unchanged)
+      s_service_name: Subscribe service name (or None to leave unchanged)
+      p_mf_1: Publish match filter element [1] (or None to leave unchanged)
+      s_mf_1: Subscribe match filter element [1] (or None to leave unchanged)
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # create configurations
+        p_config = self.create_publish_config(
+            p_dut.aware_capabilities,
+            p_type,
+            self.PAYLOAD_SIZE_TYPICAL,
+            ttl=0,
+            term_ind_on=False,
+            null_match=False)
+        if p_service_name is not None:
+            p_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = p_service_name
+        if p_mf_1 is not None:
+            p_config[
+                aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
+                    [(10).to_bytes(1, byteorder="big"), p_mf_1,
+                     bytes(range(40))])
+        s_config = self.create_publish_config(
+            s_dut.aware_capabilities,
+            s_type,
+            self.PAYLOAD_SIZE_TYPICAL,
+            ttl=0,
+            term_ind_on=False,
+            null_match=False)
+        if s_service_name is not None:
+            s_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = s_service_name
+        if s_mf_1 is not None:
+            s_config[
+                aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
+                    [(10).to_bytes(1, byteorder="big"), s_mf_1,
+                     bytes(range(40))])
+
+        p_id = p_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: start publish and wait for confirmation
+        p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Subscriber: start subscribe and wait for confirmation
+        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # Subscriber: fail on service discovery
+        if is_expected_to_pass:
+            autils.wait_for_event(s_dut,
+                                  aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        else:
+            autils.fail_on_event(s_dut,
+                                 aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+        # Publisher+Subscriber: Terminate sessions
+        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+        # verify that there were no other events (including terminations)
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+    #######################################
+    # Positive tests key:
+    #
+    # names is: test_<pub_type>_<sub_type>_<size>
+    # where:
+    #
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    # size: Size of payload fields (service name, service specific info, and match
+    # filter: typical, max, or min.
+    #######################################
+
+    @test_tracker_info(uuid="954ebbde-ed2b-4f04-9e68-88239187d69d")
+    @WifiBaseTest.wifi_test_wrap
+    def test_positive_unsolicited_passive_typical(self):
+        """Functional test case / Discovery test cases / positive test case:
+    - Solicited publish + passive subscribe
+    - Typical payload fields size
+
+    Verifies that discovery and message exchange succeeds.
+    """
+        self.positive_discovery_test_utility(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            payload_size=self.PAYLOAD_SIZE_TYPICAL)
+
+    @test_tracker_info(uuid="67fb22bb-6985-4345-95a4-90b76681a58b")
+    def test_positive_unsolicited_passive_min(self):
+        """Functional test case / Discovery test cases / positive test case:
+    - Solicited publish + passive subscribe
+    - Minimal payload fields size
+
+    Verifies that discovery and message exchange succeeds.
+    """
+        self.positive_discovery_test_utility(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            payload_size=self.PAYLOAD_SIZE_MIN)
+
+    @test_tracker_info(uuid="a02a47b9-41bb-47bb-883b-921024a2c30d")
+    def test_positive_unsolicited_passive_max(self):
+        """Functional test case / Discovery test cases / positive test case:
+    - Solicited publish + passive subscribe
+    - Maximal payload fields size
+
+    Verifies that discovery and message exchange succeeds.
+    """
+        self.positive_discovery_test_utility(
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            payload_size=self.PAYLOAD_SIZE_MAX)
+
+    @test_tracker_info(uuid="586c657f-2388-4e7a-baee-9bce2f3d1a16")
+    def test_positive_solicited_active_typical(self):
+        """Functional test case / Discovery test cases / positive test case:
+    - Unsolicited publish + active subscribe
+    - Typical payload fields size
+
+    Verifies that discovery and message exchange succeeds.
+    """
+        self.positive_discovery_test_utility(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            payload_size=self.PAYLOAD_SIZE_TYPICAL)
+
+    @test_tracker_info(uuid="5369e4ff-f406-48c5-b41a-df38ec340146")
+    def test_positive_solicited_active_min(self):
+        """Functional test case / Discovery test cases / positive test case:
+    - Unsolicited publish + active subscribe
+    - Minimal payload fields size
+
+    Verifies that discovery and message exchange succeeds.
+    """
+        self.positive_discovery_test_utility(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            payload_size=self.PAYLOAD_SIZE_MIN)
+
+    @test_tracker_info(uuid="634c6eb8-2c4f-42bd-9bbb-d874d0ec22f3")
+    def test_positive_solicited_active_max(self):
+        """Functional test case / Discovery test cases / positive test case:
+    - Unsolicited publish + active subscribe
+    - Maximal payload fields size
+
+    Verifies that discovery and message exchange succeeds.
+    """
+        self.positive_discovery_test_utility(
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            payload_size=self.PAYLOAD_SIZE_MAX)
+
+    #######################################
+    # TTL tests key:
+    #
+    # names is: test_ttl_<pub_type|sub_type>_<term_ind>
+    # where:
+    #
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    # term_ind: ind_on or ind_off
+    #######################################
+
+    @test_tracker_info(uuid="9d7e758e-e0e2-4550-bcee-bfb6a2bff63e")
+    def test_ttl_unsolicited_ind_on(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Unsolicited publish
+    - Termination indication enabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=True,
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=None,
+            term_ind_on=True)
+
+    @test_tracker_info(uuid="48fd69bc-cc2a-4f65-a0a1-63d7c1720702")
+    def test_ttl_unsolicited_ind_off(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Unsolicited publish
+    - Termination indication disabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=True,
+            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            stype=None,
+            term_ind_on=False)
+
+    @test_tracker_info(uuid="afb75fc1-9ba7-446a-b5ed-7cd37ab51b1c")
+    def test_ttl_solicited_ind_on(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Solicited publish
+    - Termination indication enabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=True,
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=None,
+            term_ind_on=True)
+
+    @test_tracker_info(uuid="703311a6-e444-4055-94ee-ea9b9b71799e")
+    def test_ttl_solicited_ind_off(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Solicited publish
+    - Termination indication disabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=True,
+            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+            stype=None,
+            term_ind_on=False)
+
+    @test_tracker_info(uuid="38a541c4-ff55-4387-87b7-4d940489da9d")
+    def test_ttl_passive_ind_on(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Passive subscribe
+    - Termination indication enabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=False,
+            ptype=None,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            term_ind_on=True)
+
+    @test_tracker_info(uuid="ba971e12-b0ca-417c-a1b5-9451598de47d")
+    def test_ttl_passive_ind_off(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Passive subscribe
+    - Termination indication disabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=False,
+            ptype=None,
+            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            term_ind_on=False)
+
+    @test_tracker_info(uuid="7b5d96f2-2415-4b98-9a51-32957f0679a0")
+    def test_ttl_active_ind_on(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Active subscribe
+    - Termination indication enabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=False,
+            ptype=None,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            term_ind_on=True)
+
+    @test_tracker_info(uuid="c9268eca-0a30-42dd-8e6c-b8b0b84697fb")
+    def test_ttl_active_ind_off(self):
+        """Functional test case / Discovery test cases / TTL test case:
+    - Active subscribe
+    - Termination indication disabled
+    """
+        self.positive_ttl_test_utility(
+            is_publish=False,
+            ptype=None,
+            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            term_ind_on=False)
+
+    #######################################
+    # Mismatched service name tests key:
+    #
+    # names is: test_mismatch_service_name_<pub_type>_<sub_type>
+    # where:
+    #
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    #######################################
+
+    @test_tracker_info(uuid="175415e9-7d07-40d0-95f0-3a5f91ea4711")
+    def test_mismatch_service_name_unsolicited_passive(self):
+        """Functional test case / Discovery test cases / Mismatch service name
+    - Unsolicited publish
+    - Passive subscribe
+    """
+        self.discovery_mismatch_test_utility(
+            is_expected_to_pass=False,
+            p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            p_service_name="GoogleTestServiceXXX",
+            s_service_name="GoogleTestServiceYYY")
+
+    @test_tracker_info(uuid="c22a54ce-9e46-47a5-ac44-831faf93d317")
+    def test_mismatch_service_name_solicited_active(self):
+        """Functional test case / Discovery test cases / Mismatch service name
+    - Solicited publish
+    - Active subscribe
+    """
+        self.discovery_mismatch_test_utility(
+            is_expected_to_pass=False,
+            p_type=aconsts.PUBLISH_TYPE_SOLICITED,
+            s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            p_service_name="GoogleTestServiceXXX",
+            s_service_name="GoogleTestServiceYYY")
+
+    #######################################
+    # Mismatched discovery session type tests key:
+    #
+    # names is: test_mismatch_service_type_<pub_type>_<sub_type>
+    # where:
+    #
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    #######################################
+
+    @test_tracker_info(uuid="4806f631-d9eb-45fd-9e75-24674962770f")
+    def test_mismatch_service_type_unsolicited_active(self):
+        """Functional test case / Discovery test cases / Mismatch service name
+    - Unsolicited publish
+    - Active subscribe
+    """
+        self.discovery_mismatch_test_utility(
+            is_expected_to_pass=True,
+            p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+    @test_tracker_info(uuid="12d648fd-b8fa-4c0f-9467-95e2366047de")
+    def test_mismatch_service_type_solicited_passive(self):
+        """Functional test case / Discovery test cases / Mismatch service name
+    - Unsolicited publish
+    - Active subscribe
+    """
+        self.discovery_mismatch_test_utility(
+            is_expected_to_pass=False,
+            p_type=aconsts.PUBLISH_TYPE_SOLICITED,
+            s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE)
+
+    #######################################
+    # Mismatched discovery match filter tests key:
+    #
+    # names is: test_mismatch_match_filter_<pub_type>_<sub_type>
+    # where:
+    #
+    # pub_type: Type of publish discovery session: unsolicited or solicited.
+    # sub_type: Type of subscribe discovery session: passive or active.
+    #######################################
+
+    @test_tracker_info(uuid="d98454cb-64af-4266-8fed-f0b545a2d7c4")
+    def test_mismatch_match_filter_unsolicited_passive(self):
+        """Functional test case / Discovery test cases / Mismatch match filter
+    - Unsolicited publish
+    - Passive subscribe
+    """
+        self.discovery_mismatch_test_utility(
+            is_expected_to_pass=False,
+            p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
+            s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            p_mf_1="hello there string",
+            s_mf_1="goodbye there string")
+
+    @test_tracker_info(uuid="663c1008-ae11-4e1a-87c7-c311d83f481c")
+    def test_mismatch_match_filter_solicited_active(self):
+        """Functional test case / Discovery test cases / Mismatch match filter
+    - Solicited publish
+    - Active subscribe
+    """
+        self.discovery_mismatch_test_utility(
+            is_expected_to_pass=False,
+            p_type=aconsts.PUBLISH_TYPE_SOLICITED,
+            s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+            p_mf_1="hello there string",
+            s_mf_1="goodbye there string")
+
+    #######################################
+    # Multiple concurrent services
+    #######################################
+
+    def run_multiple_concurrent_services(self, type_x, type_y):
+        """Validate multiple identical discovery services running on both devices:
+    - DUT1 & DUT2 running Publish for X
+    - DUT1 & DUT2 running Publish for Y
+    - DUT1 Subscribes for X
+    - DUT2 Subscribes for Y
+    Message exchanges.
+
+    Note: test requires that devices support 2 publish sessions concurrently.
+    The test will be skipped if the devices are not capable.
+
+    Args:
+      type_x, type_y: A list of [ptype, stype] of the publish and subscribe
+                      types for services X and Y respectively.
+    """
+        dut1 = self.android_devices[0]
+        dut2 = self.android_devices[1]
+
+        X_SERVICE_NAME = "ServiceXXX"
+        Y_SERVICE_NAME = "ServiceYYY"
+
+        asserts.skip_if(
+            dut1.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2
+            or dut2.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2,
+            "Devices do not support 2 publish sessions")
+
+        # attach and wait for confirmation
+        id1 = dut1.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut1, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        id2 = dut2.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut2, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # DUT1 & DUT2: start publishing both X & Y services and wait for
+        # confirmations
+        dut1_x_pid = dut1.droid.wifiAwarePublish(
+            id1, autils.create_discovery_config(X_SERVICE_NAME, type_x[0]))
+        event = autils.wait_for_event(dut1,
+                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
+                             dut1_x_pid,
+                             "Unexpected DUT1 X publish session discovery ID")
+
+        dut1_y_pid = dut1.droid.wifiAwarePublish(
+            id1, autils.create_discovery_config(Y_SERVICE_NAME, type_y[0]))
+        event = autils.wait_for_event(dut1,
+                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
+                             dut1_y_pid,
+                             "Unexpected DUT1 Y publish session discovery ID")
+
+        dut2_x_pid = dut2.droid.wifiAwarePublish(
+            id2, autils.create_discovery_config(X_SERVICE_NAME, type_x[0]))
+        event = autils.wait_for_event(dut2,
+                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
+                             dut2_x_pid,
+                             "Unexpected DUT2 X publish session discovery ID")
+
+        dut2_y_pid = dut2.droid.wifiAwarePublish(
+            id2, autils.create_discovery_config(Y_SERVICE_NAME, type_y[0]))
+        event = autils.wait_for_event(dut2,
+                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
+                             dut2_y_pid,
+                             "Unexpected DUT2 Y publish session discovery ID")
+
+        # DUT1: start subscribing for X
+        dut1_x_sid = dut1.droid.wifiAwareSubscribe(
+            id1, autils.create_discovery_config(X_SERVICE_NAME, type_x[1]))
+        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # DUT2: start subscribing for Y
+        dut2_y_sid = dut2.droid.wifiAwareSubscribe(
+            id2, autils.create_discovery_config(Y_SERVICE_NAME, type_y[1]))
+        autils.wait_for_event(dut2, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # DUT1 & DUT2: wait for service discovery
+        event = autils.wait_for_event(dut1,
+                                      aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut1_x_sid,
+            "Unexpected DUT1 X subscribe session discovery ID")
+        dut1_peer_id_for_dut2_x = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        event = autils.wait_for_event(dut2,
+                                      aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut2_y_sid,
+            "Unexpected DUT2 Y subscribe session discovery ID")
+        dut2_peer_id_for_dut1_y = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # DUT1.X send message to DUT2
+        x_msg = "Hello X on DUT2!"
+        dut1.droid.wifiAwareSendMessage(dut1_x_sid, dut1_peer_id_for_dut2_x,
+                                        self.get_next_msg_id(), x_msg,
+                                        self.msg_retx_count)
+        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+        event = autils.wait_for_event(dut2,
+                                      aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut2_x_pid,
+            "Unexpected publish session ID on DUT2 for meesage "
+            "received on service X")
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], x_msg,
+            "Message on service X from DUT1 to DUT2 not received correctly")
+
+        # DUT2.Y send message to DUT1
+        y_msg = "Hello Y on DUT1!"
+        dut2.droid.wifiAwareSendMessage(dut2_y_sid, dut2_peer_id_for_dut1_y,
+                                        self.get_next_msg_id(), y_msg,
+                                        self.msg_retx_count)
+        autils.wait_for_event(dut2, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+        event = autils.wait_for_event(dut1,
+                                      aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut1_y_pid,
+            "Unexpected publish session ID on DUT1 for meesage "
+            "received on service Y")
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], y_msg,
+            "Message on service Y from DUT2 to DUT1 not received correctly")
+
+    @test_tracker_info(uuid="eef80cf3-1fd2-4526-969b-6af2dce785d7")
+    def test_multiple_concurrent_services_both_unsolicited_passive(self):
+        """Validate multiple concurrent discovery sessions running on both devices.
+    - DUT1 & DUT2 running Publish for X
+    - DUT1 & DUT2 running Publish for Y
+    - DUT1 Subscribes for X
+    - DUT2 Subscribes for Y
+    Message exchanges.
+
+    Both sessions are Unsolicited/Passive.
+
+    Note: test requires that devices support 2 publish sessions concurrently.
+    The test will be skipped if the devices are not capable.
+    """
+        self.run_multiple_concurrent_services(
+            type_x=[
+                aconsts.PUBLISH_TYPE_UNSOLICITED,
+                aconsts.SUBSCRIBE_TYPE_PASSIVE
+            ],
+            type_y=[
+                aconsts.PUBLISH_TYPE_UNSOLICITED,
+                aconsts.SUBSCRIBE_TYPE_PASSIVE
+            ])
+
+    @test_tracker_info(uuid="46739f04-ab2b-4556-b1a4-9aa2774869b5")
+    def test_multiple_concurrent_services_both_solicited_active(self):
+        """Validate multiple concurrent discovery sessions running on both devices.
+    - DUT1 & DUT2 running Publish for X
+    - DUT1 & DUT2 running Publish for Y
+    - DUT1 Subscribes for X
+    - DUT2 Subscribes for Y
+    Message exchanges.
+
+    Both sessions are Solicited/Active.
+
+    Note: test requires that devices support 2 publish sessions concurrently.
+    The test will be skipped if the devices are not capable.
+    """
+        self.run_multiple_concurrent_services(
+            type_x=[
+                aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE
+            ],
+            type_y=[
+                aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE
+            ])
+
+    @test_tracker_info(uuid="5f8f7fd2-4a0e-4cca-8cbb-6d54353f2baa")
+    def test_multiple_concurrent_services_mix_unsolicited_solicited(self):
+        """Validate multiple concurrent discovery sessions running on both devices.
+    - DUT1 & DUT2 running Publish for X
+    - DUT1 & DUT2 running Publish for Y
+    - DUT1 Subscribes for X
+    - DUT2 Subscribes for Y
+    Message exchanges.
+
+    Session A is Unsolicited/Passive.
+    Session B is Solicited/Active.
+
+    Note: test requires that devices support 2 publish sessions concurrently.
+    The test will be skipped if the devices are not capable.
+    """
+        self.run_multiple_concurrent_services(
+            type_x=[
+                aconsts.PUBLISH_TYPE_UNSOLICITED,
+                aconsts.SUBSCRIBE_TYPE_PASSIVE
+            ],
+            type_y=[
+                aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE
+            ])
+
+    #########################################################
+
+    @test_tracker_info(uuid="908ec896-fc7a-4ee4-b633-a2f042b74448")
+    def test_upper_lower_service_name_equivalence(self):
+        """Validate that Service Name is case-insensitive. Publish a service name
+    with mixed case, subscribe to the same service name with alternative case
+    and verify that discovery happens."""
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        pub_service_name = "GoogleAbCdEf"
+        sub_service_name = "GoogleaBcDeF"
+
+        autils.create_discovery_pair(
+            p_dut,
+            s_dut,
+            p_config=autils.create_discovery_config(
+                pub_service_name, aconsts.PUBLISH_TYPE_UNSOLICITED),
+            s_config=autils.create_discovery_config(
+                sub_service_name, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+            device_startup_offset=self.device_startup_offset)
+
+    ##########################################################
+
+    def exchange_messages(self, p_dut, p_disc_id, s_dut, s_disc_id, peer_id_on_sub, session_name):
+        """
+        Exchange message between Publisher and Subscriber on target discovery session
+
+    Args:
+      p_dut: Publisher device
+      p_disc_id: Publish discovery session id
+      s_dut: Subscriber device
+      s_disc_id: Subscribe discovery session id
+      peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
+      session_name: dictionary of discovery session name base on role("pub" or "sub")
+                    {role: {disc_id: name}}
+    """
+        msg_template = "Hello {} from {} !"
+
+        # Message send from Subscriber to Publisher
+        s_to_p_msg = msg_template.format(session_name["pub"][p_disc_id],
+                                         session_name["sub"][s_disc_id])
+        s_dut.droid.wifiAwareSendMessage(s_disc_id,
+                                         peer_id_on_sub,
+                                         self.get_next_msg_id(),
+                                         s_to_p_msg,
+                                         self.msg_retx_count)
+        autils.wait_for_event(s_dut,
+                              autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_SENT, s_disc_id))
+        event = autils.wait_for_event(p_dut,
+                                      autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                                                            p_disc_id))
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], s_to_p_msg,
+            "Message on service %s from Subscriber to Publisher "
+            "not received correctly" % session_name["pub"][p_disc_id])
+        peer_id_on_pub = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Message send from Publisher to Subscriber
+        p_to_s_msg = msg_template.format(session_name["sub"][s_disc_id],
+                                         session_name["pub"][p_disc_id])
+        p_dut.droid.wifiAwareSendMessage(p_disc_id,
+                                         peer_id_on_pub,
+                                         self.get_next_msg_id(), p_to_s_msg,
+                                         self.msg_retx_count)
+        autils.wait_for_event(
+            p_dut, autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_SENT, p_disc_id))
+        event = autils.wait_for_event(s_dut,
+                                      autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                                                            s_disc_id))
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], p_to_s_msg,
+            "Message on service %s from Publisher to Subscriber"
+            "not received correctly" % session_name["sub"][s_disc_id])
+
+    def run_multiple_concurrent_services_same_name_diff_ssi(self, type_x, type_y):
+        """Validate same service name with multiple service specific info on publisher
+        and subscriber can see all service
+
+    - p_dut running Publish X and Y
+    - s_dut running subscribe A and B
+    - subscribe A find X and Y
+    - subscribe B find X and Y
+
+    Message exchanges:
+    - A to X and X to A
+    - B to X and X to B
+    - A to Y and Y to A
+    - B to Y and Y to B
+
+    Note: test requires that publisher device support 2 publish sessions concurrently,
+    and subscriber device support 2 subscribe sessions concurrently.
+    The test will be skipped if the devices are not capable.
+
+    Args:
+      type_x, type_y: A list of [ptype, stype] of the publish and subscribe
+                      types for services X and Y respectively.
+    """
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        asserts.skip_if(
+            p_dut.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2
+            or s_dut.aware_capabilities[aconsts.CAP_MAX_SUBSCRIBES] < 2,
+            "Devices do not support 2 publish sessions or 2 subscribe sessions")
+
+        SERVICE_NAME = "ServiceName"
+        X_SERVICE_SSI = "ServiceSpecificInfoXXX"
+        Y_SERVICE_SSI = "ServiceSpecificInfoYYY"
+        use_id = True
+
+        # attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
+        autils.wait_for_event(p_dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, p_id))
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False, None, use_id)
+        autils.wait_for_event(s_dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, s_id))
+
+        # Publisher: start publishing both X & Y services and wait for confirmations
+        p_disc_id_x = p_dut.droid.wifiAwarePublish(
+            p_id, autils.create_discovery_config(SERVICE_NAME, type_x[0], X_SERVICE_SSI), use_id)
+        event = autils.wait_for_event(p_dut,
+                                      autils.decorate_event(
+                                          aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id_x))
+
+        p_disc_id_y = p_dut.droid.wifiAwarePublish(
+            p_id, autils.create_discovery_config(SERVICE_NAME, type_x[0], Y_SERVICE_SSI), use_id)
+        event = autils.wait_for_event(p_dut,
+                                      autils.decorate_event(
+                                          aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id_y))
+
+        # Subscriber: start subscribe session A
+        s_disc_id_a = s_dut.droid.wifiAwareSubscribe(
+            s_id, autils.create_discovery_config(SERVICE_NAME, type_x[1]), use_id)
+        autils.wait_for_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id_a))
+
+        # Subscriber: start subscribe session B
+        s_disc_id_b = s_dut.droid.wifiAwareSubscribe(
+            p_id, autils.create_discovery_config(SERVICE_NAME, type_y[1]), use_id)
+        autils.wait_for_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id_b))
+
+        session_name = {"pub": {p_disc_id_x: "X", p_disc_id_y: "Y"},
+                        "sub": {s_disc_id_a: "A", s_disc_id_b: "B"}}
+
+        # Subscriber: subscribe session A & B wait for service discovery
+        # Number of results on each session should be exactly 2
+        results_a = {}
+        for i in range(2):
+            event = autils.wait_for_event(s_dut, autils.decorate_event(
+                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_a))
+            results_a[
+                bytes(event["data"][
+                          aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode('utf-8')] = event
+        autils.fail_on_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_a))
+
+        results_b = {}
+        for i in range(2):
+            event = autils.wait_for_event(s_dut, autils.decorate_event(
+                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_b))
+            results_b[
+                bytes(event["data"][
+                          aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode('utf-8')] = event
+        autils.fail_on_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_b))
+
+        s_a_peer_id_for_p_x = results_a[X_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        s_a_peer_id_for_p_y = results_a[Y_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        s_b_peer_id_for_p_x = results_b[X_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        s_b_peer_id_for_p_y = results_b[Y_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Message exchange between Publisher and Subscribe
+        self.exchange_messages(p_dut, p_disc_id_x,
+                               s_dut, s_disc_id_a, s_a_peer_id_for_p_x, session_name)
+
+        self.exchange_messages(p_dut, p_disc_id_x,
+                               s_dut, s_disc_id_b, s_b_peer_id_for_p_x, session_name)
+
+        self.exchange_messages(p_dut, p_disc_id_y,
+                               s_dut, s_disc_id_a, s_a_peer_id_for_p_y, session_name)
+
+        self.exchange_messages(p_dut, p_disc_id_y,
+                               s_dut, s_disc_id_b, s_b_peer_id_for_p_y, session_name)
+
+        # Check no more messages
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+        ##########################################################
+
+    @test_tracker_info(uuid="78d89d63-1cbc-47f6-a8fc-74057fea655e")
+    def test_multiple_concurrent_services_diff_ssi_unsolicited_passive(self):
+        """Multi service test on same service name but different Service Specific Info
+    - Unsolicited publish
+    - Passive subscribe
+    """
+        self.run_multiple_concurrent_services_same_name_diff_ssi(
+            type_x=[aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE],
+            type_y=[aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE])
+
+    @test_tracker_info(uuid="5d349491-48e4-4ca1-a8af-7afb44e7bcbc")
+    def test_multiple_concurrent_services_diff_ssi_solicited_active(self):
+        """Multi service test on same service name but different Service Specific Info
+    - Solicited publish
+    - Active subscribe
+    """
+        self.run_multiple_concurrent_services_same_name_diff_ssi(
+            type_x=[aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE],
+            type_y=[aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE])
diff --git a/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py b/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
new file mode 100644
index 0000000..3898832
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
@@ -0,0 +1,255 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.controllers.ap_lib.hostapd_constants import BAND_2G
+from acts.controllers.ap_lib.hostapd_constants import BAND_5G
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from scapy.all import *
+
+
+class MacRandomNoLeakageTest(AwareBaseTest, WifiBaseTest):
+    """Set of tests for Wi-Fi Aware MAC address randomization of NMI (NAN
+    management interface) and NDI (NAN data interface)."""
+
+    SERVICE_NAME = "GoogleTestServiceXYZ"
+    ping_msg = 'PING'
+
+    AWARE_DEFAULT_CHANNEL_5_BAND = 149
+    AWARE_DEFAULT_CHANNEL_24_BAND = 6
+
+    ENCR_TYPE_OPEN = 0
+    ENCR_TYPE_PASSPHRASE = 1
+    ENCR_TYPE_PMK = 2
+
+    PASSPHRASE = "This is some random passphrase - very very secure!!"
+    PMK = "ODU0YjE3YzdmNDJiNWI4NTQ2NDJjNDI3M2VkZTQyZGU="
+
+    def setup_class(self):
+        super().setup_class()
+
+        asserts.assert_true(hasattr(self, 'packet_capture'),
+                            "Needs packet_capture attribute to support sniffing.")
+        self.configure_packet_capture(channel_5g=self.AWARE_DEFAULT_CHANNEL_5_BAND,
+                                      channel_2g=self.AWARE_DEFAULT_CHANNEL_24_BAND)
+
+    def setup_test(self):
+        WifiBaseTest.setup_test(self)
+        AwareBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        WifiBaseTest.teardown_test(self)
+        AwareBaseTest.teardown_test(self)
+
+    def verify_mac_no_leakage(self, pcap_procs, factory_mac_addresses, mac_addresses):
+        # Get 2G and 5G pcaps
+        pcap_fname = '%s_%s.pcap' % (pcap_procs[BAND_5G][1], BAND_5G.upper())
+        pcap_5g = rdpcap(pcap_fname)
+
+        pcap_fname = '%s_%s.pcap' % (pcap_procs[BAND_2G][1], BAND_2G.upper())
+        pcap_2g = rdpcap(pcap_fname)
+        pcaps = pcap_5g + pcap_2g
+
+        # Verify factory MAC is not leaked in both 2G and 5G pcaps
+        ads = [self.android_devices[0], self.android_devices[1]]
+        for i, mac in enumerate(factory_mac_addresses):
+            wutils.verify_mac_not_found_in_pcap(ads[i], mac, pcaps)
+
+        # Verify random MACs are being used and in pcaps
+        for i, mac in enumerate(mac_addresses):
+            wutils.verify_mac_is_found_in_pcap(ads[i], mac, pcaps)
+
+    def transfer_mac_format(self, mac):
+        """add ':' to mac String, and transfer to lower case
+
+    Args:
+        mac: String of mac without ':'
+        @return: Lower case String of mac like "xx:xx:xx:xx:xx:xx"
+    """
+        return re.sub(r"(?<=\w)(?=(?:\w\w)+$)", ":", mac.lower())
+
+    def start_aware(self, dut, is_publish):
+        """Start Aware attach, then start Publish/Subscribe based on role
+
+     Args:
+         dut: Aware device
+         is_publish: True for Publisher, False for subscriber
+         @:return: dict with Aware discovery session info
+    """
+        aware_id = dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        event = autils.wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        mac = self.transfer_mac_format(event["data"]["mac"])
+        dut.log.info("NMI=%s", mac)
+
+        if is_publish:
+            config = autils.create_discovery_config(self.SERVICE_NAME,
+                                                    aconsts.PUBLISH_TYPE_UNSOLICITED)
+            disc_id = dut.droid.wifiAwarePublish(aware_id, config)
+            autils.wait_for_event(dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+        else:
+            config = autils.create_discovery_config(self.SERVICE_NAME,
+                                                    aconsts.SUBSCRIBE_TYPE_PASSIVE)
+            disc_id = dut.droid.wifiAwareSubscribe(aware_id, config)
+            autils.wait_for_event(dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+        aware_session = {"awareId": aware_id, "discId": disc_id, "mac": mac}
+        return aware_session
+
+    def create_date_path(self, p_dut, pub_session, s_dut, sub_session, sec_type):
+        """Create NDP based on the security type(open, PMK, PASSPHRASE), run socket connect
+
+    Args:
+        p_dut: Publish device
+        p_disc_id: Publisher discovery id
+        peer_id_on_pub: peer id on publisher
+        s_dut: Subscribe device
+        s_disc_id: Subscriber discovery id
+        peer_id_on_sub: peer id on subscriber
+        sec_type: NDP security type(open, PMK or PASSPHRASE)
+        @:return: dict with NDP info
+    """
+
+        passphrase = None
+        pmk = None
+
+        if sec_type == self.ENCR_TYPE_PASSPHRASE:
+            passphrase = self.PASSPHRASE
+        if sec_type == self.ENCR_TYPE_PMK:
+            pmk = self.PMK
+
+        p_req_key = autils.request_network(
+            p_dut,
+            p_dut.droid.wifiAwareCreateNetworkSpecifier(pub_session["discId"],
+                                                        None,
+                                                        passphrase, pmk))
+        s_req_key = autils.request_network(
+            s_dut,
+            s_dut.droid.wifiAwareCreateNetworkSpecifier(sub_session["discId"],
+                                                        sub_session["peerId"],
+                                                        passphrase, pmk))
+
+        p_net_event_nc = autils.wait_for_event_with_keys(
+            p_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+        s_net_event_nc = autils.wait_for_event_with_keys(
+            s_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+        p_net_event_lp = autils.wait_for_event_with_keys(
+            p_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+        s_net_event_lp = autils.wait_for_event_with_keys(
+            s_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+        p_aware_if = p_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        s_aware_if = s_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        p_if_mac = self.transfer_mac_format(autils.get_mac_addr(p_dut, p_aware_if))
+        p_dut.log.info("NDI %s=%s", p_aware_if, p_if_mac)
+        s_if_mac = self.transfer_mac_format(autils.get_mac_addr(s_dut, s_aware_if))
+        s_dut.log.info("NDI %s=%s", s_aware_if, s_if_mac)
+
+        s_ipv6 = p_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+        p_ipv6 = s_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+        asserts.assert_true(
+            autils.verify_socket_connect(p_dut, s_dut, p_ipv6, s_ipv6, 0),
+            "Failed socket link with Pub as Server")
+        asserts.assert_true(
+            autils.verify_socket_connect(s_dut, p_dut, s_ipv6, p_ipv6, 0),
+            "Failed socket link with Sub as Server")
+
+        ndp_info = {"pubReqKey": p_req_key, "pubIfMac": p_if_mac,
+                    "subReqKey": s_req_key, "subIfMac": s_if_mac}
+        return ndp_info
+
+    @test_tracker_info(uuid="c9c66873-a8e0-4830-8baa-ada03223bcef")
+    def test_ib_multi_data_path_mac_random_test(self):
+        """Verify there is no factory MAC Address leakage during the Aware discovery, NDP creation,
+        socket setup and IP service connection."""
+
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+        mac_addresses = []
+        factory_mac_addresses = []
+        sec_types = [self.ENCR_TYPE_PMK, self.ENCR_TYPE_PASSPHRASE]
+
+        self.log.info("Starting packet capture")
+        pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+
+        factory_mac_1 = p_dut.droid.wifigetFactorymacAddresses()[0]
+        p_dut.log.info("Factory Address: %s", factory_mac_1)
+        factory_mac_2 = s_dut.droid.wifigetFactorymacAddresses()[0]
+        s_dut.log.info("Factory Address: %s", factory_mac_2)
+        factory_mac_addresses.append(factory_mac_1)
+        factory_mac_addresses.append(factory_mac_2)
+
+        # Start Aware and exchange messages
+        publish_session = self.start_aware(p_dut, True)
+        subscribe_session = self.start_aware(s_dut, False)
+        mac_addresses.append(publish_session["mac"])
+        mac_addresses.append(subscribe_session["mac"])
+        discovery_event = autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        subscribe_session["peerId"] = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        msg_id = self.get_next_msg_id()
+        s_dut.droid.wifiAwareSendMessage(subscribe_session["discId"], subscribe_session["peerId"],
+                                         msg_id, self.ping_msg, aconsts.MAX_TX_RETRIES)
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+        pub_rx_msg_event = autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+        publish_session["peerId"] = pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        msg_id = self.get_next_msg_id()
+        p_dut.droid.wifiAwareSendMessage(publish_session["discId"], publish_session["peerId"],
+                                         msg_id, self.ping_msg, aconsts.MAX_TX_RETRIES)
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+
+        # Create Aware NDP
+        p_req_keys = []
+        s_req_keys = []
+        for sec in sec_types:
+            ndp_info = self.create_date_path(p_dut, publish_session, s_dut, subscribe_session, sec)
+            p_req_keys.append(ndp_info["pubReqKey"])
+            s_req_keys.append(ndp_info["subReqKey"])
+            mac_addresses.append(ndp_info["pubIfMac"])
+            mac_addresses.append(ndp_info["subIfMac"])
+
+        # clean-up
+        for p_req_key in p_req_keys:
+            p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        for s_req_key in s_req_keys:
+            s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+        p_dut.droid.wifiAwareDestroyAll()
+        s_dut.droid.wifiAwareDestroyAll()
+
+        self.log.info("Stopping packet capture")
+        wutils.stop_pcap(self.packet_capture, pcap_procs, False)
+
+        self.verify_mac_no_leakage(pcap_procs, factory_mac_addresses, mac_addresses)
diff --git a/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py b/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py
new file mode 100644
index 0000000..6c8d5b1
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py
@@ -0,0 +1,139 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MacRandomTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware MAC address randomization of NMI (NAN
+  management interface) and NDI (NAN data interface)."""
+
+    NUM_ITERATIONS = 10
+
+    # number of second to 'reasonably' wait to make sure that devices synchronize
+    # with each other - useful for OOB test cases, where the OOB discovery would
+    # take some time
+    WAIT_FOR_CLUSTER = 5
+
+    def request_network(self, dut, ns):
+        """Request a Wi-Fi Aware network.
+
+    Args:
+      dut: Device
+      ns: Network specifier
+    Returns: the request key
+    """
+        network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+        return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+    ##########################################################################
+
+    @test_tracker_info(uuid="09964368-146a-48e4-9f33-6a319f9eeadc")
+    def test_nmi_ndi_randomization_on_enable(self):
+        """Validate randomization of the NMI (NAN management interface) and all NDIs
+    (NAN data-interface) on each enable/disable cycle"""
+        dut = self.android_devices[0]
+
+        # re-enable randomization interval (since if disabled it may also disable
+        # the 'randomize on enable' feature).
+        autils.configure_mac_random_interval(dut, 1800)
+
+        # DUT: attach and wait for confirmation & identity 10 times
+        mac_addresses = {}
+        for i in range(self.NUM_ITERATIONS):
+            id = dut.droid.wifiAwareAttach(True)
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+            ident_event = autils.wait_for_event(
+                dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+            # process NMI
+            mac = ident_event["data"]["mac"]
+            dut.log.info("NMI=%s", mac)
+            if mac in mac_addresses:
+                mac_addresses[mac] = mac_addresses[mac] + 1
+            else:
+                mac_addresses[mac] = 1
+
+            # process NDIs
+            time.sleep(5)  # wait for NDI creation to complete
+            for j in range(
+                    dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]):
+                ndi_interface = "%s%d" % (aconsts.AWARE_NDI_PREFIX, j)
+                ndi_mac = autils.get_mac_addr(dut, ndi_interface)
+                dut.log.info("NDI %s=%s", ndi_interface, ndi_mac)
+                if ndi_mac in mac_addresses:
+                    mac_addresses[ndi_mac] = mac_addresses[ndi_mac] + 1
+                else:
+                    mac_addresses[ndi_mac] = 1
+
+            dut.droid.wifiAwareDestroy(id)
+
+        # Test for uniqueness
+        for mac in mac_addresses.keys():
+            if mac_addresses[mac] != 1:
+                asserts.fail("MAC address %s repeated %d times (all=%s)" %
+                             (mac, mac_addresses[mac], mac_addresses))
+
+        # Verify that infra interface (e.g. wlan0) MAC address is not used for NMI
+        infra_mac = autils.get_wifi_mac_address(dut)
+        asserts.assert_false(
+            infra_mac in mac_addresses,
+            "Infrastructure MAC address (%s) is used for Aware NMI (all=%s)" %
+            (infra_mac, mac_addresses))
+
+    @test_tracker_info(uuid="0fb0b5d8-d9cb-4e37-b9af-51811be5670d")
+    def test_nmi_randomization_on_interval(self):
+        """Validate randomization of the NMI (NAN management interface) on a set
+    interval. Default value is 30 minutes - change to a small value to allow
+    testing in real-time"""
+        RANDOM_INTERVAL = 120  # minimal value in current implementation
+
+        dut = self.android_devices[0]
+
+        # set randomization interval to 120 seconds
+        autils.configure_mac_random_interval(dut, RANDOM_INTERVAL)
+
+        # attach and wait for first identity
+        id = dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        ident_event = autils.wait_for_event(
+            dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        mac1 = ident_event["data"]["mac"]
+
+        # wait for second identity callback
+        # Note: exact randomization interval is not critical, just approximate,
+        # hence giving a few more seconds.
+        ident_event = autils.wait_for_event(
+            dut,
+            aconsts.EVENT_CB_ON_IDENTITY_CHANGED,
+            timeout=RANDOM_INTERVAL + 5)
+        mac2 = ident_event["data"]["mac"]
+
+        # validate MAC address is randomized
+        asserts.assert_false(
+            mac1 == mac2,
+            "Randomized MAC addresses (%s, %s) should be different" % (mac1,
+                                                                       mac2))
+
+        # clean-up
+        dut.droid.wifiAwareDestroy(id)
diff --git a/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py b/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py
new file mode 100644
index 0000000..e008e12
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py
@@ -0,0 +1,190 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import base64
+import time
+import queue
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MatchFilterTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware Discovery Match Filter behavior. These all
+  use examples from Appendix H of the Wi-Fi Aware standard."""
+
+    SERVICE_NAME = "GoogleTestServiceMFMFMF"
+
+    MF_NNNNN = bytes([0x0, 0x0, 0x0, 0x0, 0x0])
+    MF_12345 = bytes([0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x4, 0x1, 0x5])
+    MF_12145 = bytes([0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x4, 0x1, 0x5])
+    MF_1N3N5 = bytes([0x1, 0x1, 0x0, 0x1, 0x3, 0x0, 0x1, 0x5])
+    MF_N23N5 = bytes([0x0, 0x1, 0x2, 0x1, 0x3, 0x0, 0x1, 0x5])
+    MF_N2N4 = bytes([0x0, 0x1, 0x2, 0x0, 0x1, 0x4])
+    MF_1N3N = bytes([0x1, 0x1, 0x0, 0x1, 0x3, 0x0])
+
+    # Set of sample match filters from the spec. There is a set of matched
+    # filters:
+    # - Filter 1
+    # - Filter 2
+    # - Expected to match if the Subscriber uses Filter 1 as Tx and the Publisher
+    #   uses Filter 2 as Rx (implies Solicited/Active)
+    # - (the reverse) Expected to match if the Publisher uses Filter 1 as Tx and
+    #   the Subscriber uses Filter 2 as Rx (implies Unsolicited/Passive)
+    match_filters = [[None, None, True, True], [None, MF_NNNNN, True, True], [
+        MF_NNNNN, None, True, True
+    ], [None, MF_12345, True, False], [MF_12345, None, False, True], [
+        MF_NNNNN, MF_12345, True, True
+    ], [MF_12345, MF_NNNNN, True, True], [MF_12345, MF_12345, True, True], [
+        MF_12345, MF_12145, False, False
+    ], [MF_1N3N5, MF_12345, True, True], [MF_12345, MF_N23N5, True, True],
+                     [MF_N2N4, MF_12345, True,
+                      False], [MF_12345, MF_1N3N, False, True]]
+
+    def run_discovery(self, p_dut, s_dut, p_mf, s_mf, do_unsolicited_passive,
+                      expect_discovery):
+        """Creates a discovery session (publish and subscribe) with the specified
+    configuration.
+
+    Args:
+      p_dut: Device to use as publisher.
+      s_dut: Device to use as subscriber.
+      p_mf: Publish's match filter.
+      s_mf: Subscriber's match filter.
+      do_unsolicited_passive: True to use an Unsolicited/Passive discovery,
+                              False for a Solicited/Active discovery session.
+      expect_discovery: True if service should be discovered, False otherwise.
+    Returns: True on success, False on failure (based on expect_discovery arg)
+    """
+        # Encode the match filters
+        p_mf = base64.b64encode(p_mf).decode(
+            "utf-8") if p_mf is not None else None
+        s_mf = base64.b64encode(s_mf).decode(
+            "utf-8") if s_mf is not None else None
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach()
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach()
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: start publish and wait for confirmation
+        p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.create_discovery_config(
+                self.SERVICE_NAME,
+                d_type=aconsts.PUBLISH_TYPE_UNSOLICITED
+                if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED,
+                match_filter=p_mf))
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Subscriber: start subscribe and wait for confirmation
+        s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.create_discovery_config(
+                self.SERVICE_NAME,
+                d_type=aconsts.SUBSCRIBE_TYPE_PASSIVE
+                if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                match_filter=s_mf))
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # Subscriber: wait or fail on service discovery
+        event = None
+        try:
+            event = s_dut.ed.pop_event(
+                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, autils.EVENT_TIMEOUT)
+            s_dut.log.info("[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s",
+                           event)
+        except queue.Empty:
+            s_dut.log.info("[Subscriber] No SESSION_CB_ON_SERVICE_DISCOVERED")
+
+        # clean-up
+        p_dut.droid.wifiAwareDestroy(p_id)
+        s_dut.droid.wifiAwareDestroy(s_id)
+
+        if expect_discovery:
+            return event is not None
+        else:
+            return event is None
+
+    def run_match_filters_per_spec(self, do_unsolicited_passive):
+        """Validate all the match filter combinations in the Wi-Fi Aware spec,
+    Appendix H.
+
+    Args:
+      do_unsolicited_passive: True to run the Unsolicited/Passive tests, False
+                              to run the Solicited/Active tests.
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        fails = []
+        for i in range(len(self.match_filters)):
+            test_info = self.match_filters[i]
+            if do_unsolicited_passive:
+                pub_type = "Unsolicited"
+                sub_type = "Passive"
+                pub_mf = test_info[0]
+                sub_mf = test_info[1]
+                expect_discovery = test_info[3]
+            else:
+                pub_type = "Solicited"
+                sub_type = "Active"
+                pub_mf = test_info[1]
+                sub_mf = test_info[0]
+                expect_discovery = test_info[2]
+
+            self.log.info("Test #%d: %s Pub MF=%s, %s Sub MF=%s: Discovery %s",
+                          i, pub_type, pub_mf, sub_type, sub_mf, "EXPECTED"
+                          if test_info[2] else "UNEXPECTED")
+            result = self.run_discovery(
+                p_dut,
+                s_dut,
+                p_mf=pub_mf,
+                s_mf=sub_mf,
+                do_unsolicited_passive=do_unsolicited_passive,
+                expect_discovery=expect_discovery)
+            self.log.info("Test #%d %s Pub/%s Sub %s", i, pub_type, sub_type,
+                          "PASS" if result else "FAIL")
+            if not result:
+                fails.append(i)
+
+        asserts.assert_true(
+            len(fails) == 0,
+            "Some match filter tests are failing",
+            extras={"data": fails})
+
+    ###############################################################
+
+    @test_tracker_info(uuid="bd734f8c-895a-4cf9-820f-ec5060517fe9")
+    def test_match_filters_per_spec_unsolicited_passive(self):
+        """Validate all the match filter combinations in the Wi-Fi Aware spec,
+    Appendix H for Unsolicited Publish (tx filter) Passive Subscribe (rx
+    filter)"""
+        self.run_match_filters_per_spec(do_unsolicited_passive=True)
+
+    @test_tracker_info(uuid="6560124d-69e5-49ff-a7e5-3cb305983723")
+    def test_match_filters_per_spec_solicited_active(self):
+        """Validate all the match filter combinations in the Wi-Fi Aware spec,
+    Appendix H for Solicited Publish (rx filter) Active Subscribe (tx
+    filter)"""
+        self.run_match_filters_per_spec(do_unsolicited_passive=False)
diff --git a/acts_tests/tests/google/wifi/aware/functional/MessageTest.py b/acts_tests/tests/google/wifi/aware/functional/MessageTest.py
new file mode 100644
index 0000000..040f4e4
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/MessageTest.py
@@ -0,0 +1,462 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import string
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MessageTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+
+    # configuration parameters used by tests
+    PAYLOAD_SIZE_MIN = 0
+    PAYLOAD_SIZE_TYPICAL = 1
+    PAYLOAD_SIZE_MAX = 2
+
+    NUM_MSGS_NO_QUEUE = 10
+    NUM_MSGS_QUEUE_DEPTH_MULT = 2  # number of messages = mult * queue depth
+
+    def create_msg(self, caps, payload_size, id):
+        """Creates a message string of the specified size containing the input id.
+
+    Args:
+      caps: Device capabilities.
+      payload_size: The size of the message to create - min (null or empty
+                    message), typical, max (based on device capabilities). Use
+                    the PAYLOAD_SIZE_xx constants.
+      id: Information to include in the generated message (or None).
+
+    Returns: A string of the requested size, optionally containing the id.
+    """
+        if payload_size == self.PAYLOAD_SIZE_MIN:
+            # arbitrarily return a None or an empty string (equivalent messages)
+            return None if id % 2 == 0 else ""
+        elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
+            return "*** ID=%d ***" % id + string.ascii_uppercase
+        else:  # PAYLOAD_SIZE_MAX
+            return "*** ID=%4d ***" % id + "M" * (
+                caps[aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN] - 15)
+
+    def create_config(self, is_publish, extra_diff=None):
+        """Create a base configuration based on input parameters.
+
+    Args:
+      is_publish: True for publish, False for subscribe sessions.
+      extra_diff: String to add to service name: allows differentiating
+                  discovery sessions.
+
+    Returns:
+      publish discovery configuration object.
+    """
+        config = {}
+        if is_publish:
+            config[
+                aconsts.
+                DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.PUBLISH_TYPE_UNSOLICITED
+        else:
+            config[
+                aconsts.
+                DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.SUBSCRIBE_TYPE_PASSIVE
+        config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX" + (
+            extra_diff if extra_diff is not None else "")
+        return config
+
+    def prep_message_exchange(self, extra_diff=None):
+        """Creates a discovery session (publish and subscribe), and waits for
+    service discovery - at that point the sessions are ready for message
+    exchange.
+
+    Args:
+      extra_diff: String to add to service name: allows differentiating
+                  discovery sessions.
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # if differentiating (multiple) sessions then should decorate events with id
+        use_id = extra_diff is not None
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
+        autils.wait_for_event(
+            p_dut, aconsts.EVENT_CB_ON_ATTACHED
+            if not use_id else autils.decorate_event(
+                aconsts.EVENT_CB_ON_ATTACHED, p_id))
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False, None, use_id)
+        autils.wait_for_event(
+            s_dut, aconsts.EVENT_CB_ON_ATTACHED
+            if not use_id else autils.decorate_event(
+                aconsts.EVENT_CB_ON_ATTACHED, s_id))
+
+        # Publisher: start publish and wait for confirmation
+        p_disc_id = p_dut.droid.wifiAwarePublish(
+            p_id, self.create_config(True, extra_diff=extra_diff), use_id)
+        autils.wait_for_event(
+            p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED
+            if not use_id else autils.decorate_event(
+                aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id))
+
+        # Subscriber: start subscribe and wait for confirmation
+        s_disc_id = s_dut.droid.wifiAwareSubscribe(
+            s_id, self.create_config(False, extra_diff=extra_diff), use_id)
+        autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+            if not use_id else autils.decorate_event(
+                aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id))
+
+        # Subscriber: wait for service discovery
+        discovery_event = autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED
+            if not use_id else autils.decorate_event(
+                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id))
+        peer_id_on_sub = discovery_event["data"][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+
+        return {
+            "p_dut": p_dut,
+            "s_dut": s_dut,
+            "p_id": p_id,
+            "s_id": s_id,
+            "p_disc_id": p_disc_id,
+            "s_disc_id": s_disc_id,
+            "peer_id_on_sub": peer_id_on_sub
+        }
+
+    def run_message_no_queue(self, payload_size):
+        """Validate L2 message exchange between publisher & subscriber with no
+    queueing - i.e. wait for an ACK on each message before sending the next
+    message.
+
+    Args:
+      payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
+    """
+        discovery_info = self.prep_message_exchange()
+        p_dut = discovery_info["p_dut"]
+        s_dut = discovery_info["s_dut"]
+        p_disc_id = discovery_info["p_disc_id"]
+        s_disc_id = discovery_info["s_disc_id"]
+        peer_id_on_sub = discovery_info["peer_id_on_sub"]
+
+        for i in range(self.NUM_MSGS_NO_QUEUE):
+            msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+            msg_id = self.get_next_msg_id()
+            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
+                                             msg, 0)
+            tx_event = autils.wait_for_event(
+                s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+            rx_event = autils.wait_for_event(
+                p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+            asserts.assert_equal(
+                msg_id, tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+                "Subscriber -> Publisher message ID corrupted")
+            autils.assert_equal_strings(
+                msg,
+                rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+                "Subscriber -> Publisher message %d corrupted" % i)
+
+        peer_id_on_pub = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        for i in range(self.NUM_MSGS_NO_QUEUE):
+            msg = self.create_msg(s_dut.aware_capabilities, payload_size,
+                                  1000 + i)
+            msg_id = self.get_next_msg_id()
+            p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id,
+                                             msg, 0)
+            tx_event = autils.wait_for_event(
+                p_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+            rx_event = autils.wait_for_event(
+                s_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+            asserts.assert_equal(
+                msg_id, tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+                "Publisher -> Subscriber message ID corrupted")
+            autils.assert_equal_strings(
+                msg,
+                rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+                "Publisher -> Subscriber message %d corrupted" % i)
+
+        # verify there are no more events
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+    def wait_for_messages(self,
+                          tx_msgs,
+                          tx_msg_ids,
+                          tx_disc_id,
+                          rx_disc_id,
+                          tx_dut,
+                          rx_dut,
+                          are_msgs_empty=False):
+        """Validate that all expected messages are transmitted correctly and
+    received as expected. Method is called after the messages are sent into
+    the transmission queue.
+
+    Note: that message can be transmitted and received out-of-order (which is
+    acceptable and the method handles that correctly).
+
+    Args:
+      tx_msgs: dictionary of transmitted messages
+      tx_msg_ids: dictionary of transmitted message ids
+      tx_disc_id: transmitter discovery session id (None for no decoration)
+      rx_disc_id: receiver discovery session id (None for no decoration)
+      tx_dut: transmitter device
+      rx_dut: receiver device
+      are_msgs_empty: True if the messages are None or empty (changes dup detection)
+
+    Returns: the peer ID from any of the received messages
+    """
+        # peer id on receiver
+        peer_id_on_rx = None
+
+        # wait for all messages to be transmitted
+        still_to_be_tx = len(tx_msg_ids)
+        while still_to_be_tx != 0:
+            tx_event = autils.wait_for_event(
+                tx_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT
+                if tx_disc_id is None else autils.decorate_event(
+                    aconsts.SESSION_CB_ON_MESSAGE_SENT, tx_disc_id))
+            tx_msg_id = tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
+            tx_msg_ids[tx_msg_id] = tx_msg_ids[tx_msg_id] + 1
+            if tx_msg_ids[tx_msg_id] == 1:
+                still_to_be_tx = still_to_be_tx - 1
+
+        # check for any duplicate transmit notifications
+        asserts.assert_equal(
+            len(tx_msg_ids), sum(tx_msg_ids.values()),
+            "Duplicate transmit message IDs: %s" % tx_msg_ids)
+
+        # wait for all messages to be received
+        still_to_be_rx = len(tx_msg_ids)
+        while still_to_be_rx != 0:
+            rx_event = autils.wait_for_event(
+                rx_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED
+                if rx_disc_id is None else autils.decorate_event(
+                    aconsts.SESSION_CB_ON_MESSAGE_RECEIVED, rx_disc_id))
+            peer_id_on_rx = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+            if are_msgs_empty:
+                still_to_be_rx = still_to_be_rx - 1
+            else:
+                rx_msg = rx_event["data"][
+                    aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+                asserts.assert_true(
+                    rx_msg in tx_msgs,
+                    "Received a message we did not send!? -- '%s'" % rx_msg)
+                tx_msgs[rx_msg] = tx_msgs[rx_msg] + 1
+                if tx_msgs[rx_msg] == 1:
+                    still_to_be_rx = still_to_be_rx - 1
+
+        # check for any duplicate received messages
+        if not are_msgs_empty:
+            asserts.assert_equal(
+                len(tx_msgs), sum(tx_msgs.values()),
+                "Duplicate transmit messages: %s" % tx_msgs)
+
+        return peer_id_on_rx
+
+    def run_message_with_queue(self, payload_size):
+        """Validate L2 message exchange between publisher & subscriber with
+    queueing - i.e. transmit all messages and then wait for ACKs.
+
+    Args:
+      payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
+    """
+        discovery_info = self.prep_message_exchange()
+        p_dut = discovery_info["p_dut"]
+        s_dut = discovery_info["s_dut"]
+        p_disc_id = discovery_info["p_disc_id"]
+        s_disc_id = discovery_info["s_disc_id"]
+        peer_id_on_sub = discovery_info["peer_id_on_sub"]
+
+        msgs = {}
+        msg_ids = {}
+        for i in range(
+                self.NUM_MSGS_QUEUE_DEPTH_MULT * s_dut.
+                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+            msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+            msg_id = self.get_next_msg_id()
+            msgs[msg] = 0
+            msg_ids[msg_id] = 0
+            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
+                                             msg, 0)
+        peer_id_on_pub = self.wait_for_messages(
+            msgs, msg_ids, None, None, s_dut, p_dut,
+            payload_size == self.PAYLOAD_SIZE_MIN)
+
+        msgs = {}
+        msg_ids = {}
+        for i in range(
+                self.NUM_MSGS_QUEUE_DEPTH_MULT * p_dut.
+                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+            msg = self.create_msg(p_dut.aware_capabilities, payload_size,
+                                  1000 + i)
+            msg_id = self.get_next_msg_id()
+            msgs[msg] = 0
+            msg_ids[msg_id] = 0
+            p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id,
+                                             msg, 0)
+        self.wait_for_messages(msgs, msg_ids, None, None, p_dut, s_dut,
+                               payload_size == self.PAYLOAD_SIZE_MIN)
+
+        # verify there are no more events
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+    def run_message_multi_session_with_queue(self, payload_size):
+        """Validate L2 message exchange between publishers & subscribers with
+    queueing - i.e. transmit all messages and then wait for ACKs. Uses 2
+    discovery sessions running concurrently and validates that messages
+    arrive at the correct destination.
+
+    Args:
+      payload_size: min, typical, or max (PAYLOAD_SIZE_xx)
+    """
+        discovery_info1 = self.prep_message_exchange(extra_diff="-111")
+        p_dut = discovery_info1["p_dut"]  # same for both sessions
+        s_dut = discovery_info1["s_dut"]  # same for both sessions
+        p_disc_id1 = discovery_info1["p_disc_id"]
+        s_disc_id1 = discovery_info1["s_disc_id"]
+        peer_id_on_sub1 = discovery_info1["peer_id_on_sub"]
+
+        discovery_info2 = self.prep_message_exchange(extra_diff="-222")
+        p_disc_id2 = discovery_info2["p_disc_id"]
+        s_disc_id2 = discovery_info2["s_disc_id"]
+        peer_id_on_sub2 = discovery_info2["peer_id_on_sub"]
+
+        msgs1 = {}
+        msg_ids1 = {}
+        msgs2 = {}
+        msg_ids2 = {}
+        for i in range(
+                self.NUM_MSGS_QUEUE_DEPTH_MULT * s_dut.
+                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+            msg1 = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+            msg_id1 = self.get_next_msg_id()
+            msgs1[msg1] = 0
+            msg_ids1[msg_id1] = 0
+            s_dut.droid.wifiAwareSendMessage(s_disc_id1, peer_id_on_sub1,
+                                             msg_id1, msg1, 0)
+            msg2 = self.create_msg(s_dut.aware_capabilities, payload_size,
+                                   100 + i)
+            msg_id2 = self.get_next_msg_id()
+            msgs2[msg2] = 0
+            msg_ids2[msg_id2] = 0
+            s_dut.droid.wifiAwareSendMessage(s_disc_id2, peer_id_on_sub2,
+                                             msg_id2, msg2, 0)
+
+        peer_id_on_pub1 = self.wait_for_messages(
+            msgs1, msg_ids1, s_disc_id1, p_disc_id1, s_dut, p_dut,
+            payload_size == self.PAYLOAD_SIZE_MIN)
+        peer_id_on_pub2 = self.wait_for_messages(
+            msgs2, msg_ids2, s_disc_id2, p_disc_id2, s_dut, p_dut,
+            payload_size == self.PAYLOAD_SIZE_MIN)
+
+        msgs1 = {}
+        msg_ids1 = {}
+        msgs2 = {}
+        msg_ids2 = {}
+        for i in range(
+                self.NUM_MSGS_QUEUE_DEPTH_MULT * p_dut.
+                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+            msg1 = self.create_msg(p_dut.aware_capabilities, payload_size,
+                                   1000 + i)
+            msg_id1 = self.get_next_msg_id()
+            msgs1[msg1] = 0
+            msg_ids1[msg_id1] = 0
+            p_dut.droid.wifiAwareSendMessage(p_disc_id1, peer_id_on_pub1,
+                                             msg_id1, msg1, 0)
+            msg2 = self.create_msg(p_dut.aware_capabilities, payload_size,
+                                   1100 + i)
+            msg_id2 = self.get_next_msg_id()
+            msgs2[msg2] = 0
+            msg_ids2[msg_id2] = 0
+            p_dut.droid.wifiAwareSendMessage(p_disc_id2, peer_id_on_pub2,
+                                             msg_id2, msg2, 0)
+
+        self.wait_for_messages(msgs1, msg_ids1, p_disc_id1, s_disc_id1, p_dut,
+                               s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
+        self.wait_for_messages(msgs2, msg_ids2, p_disc_id2, s_disc_id2, p_dut,
+                               s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
+
+        # verify there are no more events
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+    ############################################################################
+
+    @test_tracker_info(uuid="a8cd0512-b279-425f-93cf-949ddba22c7a")
+    @WifiBaseTest.wifi_test_wrap
+    def test_message_no_queue_min(self):
+        """Functional / Message / No queue
+    - Minimal payload size (None or "")
+    """
+        self.run_message_no_queue(self.PAYLOAD_SIZE_MIN)
+
+    @test_tracker_info(uuid="2c26170a-5d0a-4cf4-b0b9-56ef03f5dcf4")
+    def test_message_no_queue_typical(self):
+        """Functional / Message / No queue
+    - Typical payload size
+    """
+        self.run_message_no_queue(self.PAYLOAD_SIZE_TYPICAL)
+
+    @test_tracker_info(uuid="c984860c-b62d-4d9b-8bce-4d894ea3bfbe")
+    @WifiBaseTest.wifi_test_wrap
+    def test_message_no_queue_max(self):
+        """Functional / Message / No queue
+    - Max payload size (based on device capabilities)
+    """
+        self.run_message_no_queue(self.PAYLOAD_SIZE_MAX)
+
+    @test_tracker_info(uuid="3f06de73-31ab-4e0c-bc6f-59abdaf87f4f")
+    def test_message_with_queue_min(self):
+        """Functional / Message / With queue
+    - Minimal payload size (none or "")
+    """
+        self.run_message_with_queue(self.PAYLOAD_SIZE_MIN)
+
+    @test_tracker_info(uuid="9b7f5bd8-b0b1-479e-8e4b-9db0bb56767b")
+    def test_message_with_queue_typical(self):
+        """Functional / Message / With queue
+    - Typical payload size
+    """
+        self.run_message_with_queue(self.PAYLOAD_SIZE_TYPICAL)
+
+    @test_tracker_info(uuid="4f9a6dce-3050-4e6a-a143-53592c6c7c28")
+    def test_message_with_queue_max(self):
+        """Functional / Message / With queue
+    - Max payload size (based on device capabilities)
+    """
+        self.run_message_with_queue(self.PAYLOAD_SIZE_MAX)
+
+    @test_tracker_info(uuid="4cece232-0983-4d6b-90a9-1bb9314b64f0")
+    def test_message_with_multiple_discovery_sessions_typical(self):
+        """Functional / Message / Multiple sessions
+
+     Sets up 2 discovery sessions on 2 devices. Sends a message in each
+     direction on each discovery session and verifies that reaches expected
+     destination.
+    """
+        self.run_message_multi_session_with_queue(self.PAYLOAD_SIZE_TYPICAL)
diff --git a/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py b/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
new file mode 100644
index 0000000..a92552b
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
@@ -0,0 +1,237 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi import wifi_constants as wconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+# arbitrary timeout for events
+EVENT_TIMEOUT = 10
+
+
+class NonConcurrencyTest(AwareBaseTest):
+    """Tests lack of concurrency scenarios Wi-Fi Aware with WFD (p2p) and
+  SoftAP
+
+  Note: these tests should be modified if the concurrency behavior changes!"""
+
+    SERVICE_NAME = "GoogleTestXYZ"
+    TETHER_SSID = "GoogleTestSoftApXYZ"
+
+    def teardown_test(self):
+        AwareBaseTest.teardown_test(self)
+        for ad in self.android_devices:
+            ad.droid.wifiP2pClose()
+            ad.droid.connectivityStopTethering(0)
+
+    def run_aware_then_incompat_service(self, is_p2p):
+        """Run test to validate that a running Aware session terminates when an
+    Aware-incompatible service is started.
+    P2P: has same priority, will bring down Aware, then Aware will become available again.
+    SoftAp: has higher priority, will bring down Aware, Aware will keep unavailable.
+
+    Args:
+      is_p2p: True for p2p, False for SoftAP
+    """
+        dut = self.android_devices[0]
+
+        # start Aware
+        id = dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        time.sleep(EVENT_TIMEOUT)
+
+        # start other service
+        if is_p2p:
+            dut.droid.wifiP2pInitialize()
+        else:
+            wutils.start_wifi_tethering(dut, self.TETHER_SSID, password=None)
+
+        # expect an announcement about Aware non-availability
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+        if is_p2p:
+            # P2P has same priority, aware will be available
+            autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+        else:
+            # SoftAp has higher priority, aware will keep unavailable
+            autils.fail_on_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+            # local clean-up
+            wutils.stop_wifi_tethering(dut)
+
+    def run_incompat_service_then_aware(self, is_p2p):
+        """Validate that if an Aware-incompatible service is already up then try to start Aware.
+    P2P: has same priority, Aware can bring it down.
+    SoftAp: has higher priority, Aware will be unavailable, any Aware operation will fail.
+
+    Args:
+      is_p2p: True for p2p, False for SoftAP
+    """
+        dut = self.android_devices[0]
+
+        # start other service
+        if is_p2p:
+            dut.droid.wifiP2pInitialize()
+            # expect no announcement about Aware non-availability
+            autils.fail_on_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+        else:
+            wutils.start_wifi_tethering(dut, self.TETHER_SSID, password=None)
+            # expect an announcement about Aware non-availability
+            autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+        # Change Wifi state and location mode to check if aware became available. 
+        wutils.wifi_toggle_state(dut, False)
+        utils.set_location_service(dut, False)
+        wutils.wifi_toggle_state(dut, True)
+        utils.set_location_service(dut, True)
+
+        if is_p2p:
+            # P2P has same priority, aware will be available
+            autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+            asserts.assert_true(dut.droid.wifiIsAwareAvailable(), "Aware should be available")
+        else:
+            # SoftAp has higher priority, aware will keep unavailable
+            autils.fail_on_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+            asserts.assert_false(dut.droid.wifiIsAwareAvailable(),
+                                 "Aware is available (it shouldn't be)")
+
+        dut.droid.wifiAwareAttach()
+        if is_p2p:
+            # P2P has same priority, Aware attach should success.
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        else:
+            # SoftAp has higher priority, Aware attach should fail.
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+
+        if not is_p2p:
+            wutils.stop_wifi_tethering(dut)
+
+            # expect an announcement about Aware availability
+            autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+            # try starting Aware
+            dut.droid.wifiAwareAttach()
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    def run_aware_then_connect_to_new_ap(self):
+        """Validate interaction of Wi-Fi Aware and infra (STA) association with randomized MAC
+    address. Such an association may trigger interface down and up - possibly disrupting a Wi-Fi
+    Aware session.
+
+    Test behavior:
+    - Start Aware
+    - Associate STA
+    - Check if an Aware state change Broadcast received
+    - If necessary (Broadcast received) restart Aware
+    - Start publish
+    - Start Subscribe on peer
+    - Verify discovery
+    """
+        dut = self.android_devices[0]
+        dut_ap = self.android_devices[1]
+        wutils.reset_wifi(dut)
+        wutils.reset_wifi(dut_ap)
+        p_config = autils.create_discovery_config(self.SERVICE_NAME,
+                                                  aconsts.PUBLISH_TYPE_UNSOLICITED)
+        s_config = autils.create_discovery_config(self.SERVICE_NAME,
+                                                  aconsts.SUBSCRIBE_TYPE_PASSIVE)
+
+        # create random SSID and start softAp on dut_ap
+        ap_ssid = self.TETHER_SSID + utils.rand_ascii_str(8)
+        ap_password = utils.rand_ascii_str(8)
+        config = {wutils.WifiEnums.SSID_KEY: ap_ssid, wutils.WifiEnums.PWD_KEY: ap_password}
+        wutils.start_wifi_tethering(dut_ap, ap_ssid, ap_password)
+        asserts.assert_true(dut_ap.droid.wifiIsApEnabled(),
+                            "SoftAp is not reported as running")
+
+        # dut start Aware attach and connect to softAp on dut_ap
+        p_id = dut.droid.wifiAwareAttach()
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        wutils.start_wifi_connection_scan_and_ensure_network_found(dut, ap_ssid)
+        wutils.wifi_connect(dut, config, check_connectivity=False)
+        autils.wait_for_event(dut, wconsts.WIFI_STATE_CHANGED)
+
+        # Check if the WifiAwareState changes then restart the Aware
+        state_change = False
+        try:
+            dut.ed.pop_event(aconsts.BROADCAST_WIFI_AWARE_AVAILABLE, EVENT_TIMEOUT)
+            dut.log.info(aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+            p_id = dut.droid.wifiAwareAttach()
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+            wutils.ensure_no_disconnect(dut)
+            state_change = True
+        except queue.Empty:
+            dut.log.info('WifiAware state was not changed')
+
+        # dut_ap stop softAp and start publish
+        wutils.stop_wifi_tethering(dut_ap)
+        autils.wait_for_event(dut_ap, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+        s_id = dut_ap.droid.wifiAwareAttach()
+        autils.wait_for_event(dut_ap, aconsts.EVENT_CB_ON_ATTACHED)
+        s_disc_id = dut_ap.droid.wifiAwarePublish(s_id, s_config)
+        autils.wait_for_event(dut_ap, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # dut start subscribe
+        wutils.wait_for_disconnect(dut)
+        if state_change:
+            autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+            p_id = dut.droid.wifiAwareAttach()
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        p_disc_id = dut.droid.wifiAwareSubscribe(p_id, p_config)
+        autils.wait_for_event(dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # Check discovery session
+        autils.wait_for_event(dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+    ##########################################################################
+
+    @test_tracker_info(uuid="b7c84cbe-d744-440a-9279-a0133e88e8cb")
+    def test_run_p2p_then_aware(self):
+        """Validate that if p2p is already up then any Aware operation fails"""
+        self.run_incompat_service_then_aware(is_p2p=True)
+
+    @test_tracker_info(uuid="1e7b3a6d-575d-4911-80bb-6fcf1157ee9f")
+    def test_run_aware_then_p2p(self):
+        """Validate that a running Aware session terminates when p2p is started"""
+        self.run_aware_then_incompat_service(is_p2p=True)
+
+    @test_tracker_info(uuid="82a0bd98-3022-4831-ac9e-d81f58c742d2")
+    def test_run_softap_then_aware(self):
+        """Validate that if SoftAp is already up then any Aware operation fails"""
+        asserts.skip_if(
+            self.android_devices[0].model not in self.dbs_supported_models,
+            "Device %s doesn't support STA+AP." % self.android_devices[0].model)
+        self.run_incompat_service_then_aware(is_p2p=False)
+
+    @test_tracker_info(uuid="0da7661e-8ac2-4f68-b6d3-b3f612369d03")
+    def test_run_aware_then_softap(self):
+        """Validate that a running Aware session terminates when softAp is
+    started"""
+        self.run_aware_then_incompat_service(is_p2p=False)
+
+    @test_tracker_info(uuid="2ac27ac6-8010-4d05-b892-00242420b075")
+    def test_run_aware_then_connect_new_ap(self):
+        """Validate connect new ap during Aware session"""
+        self.run_aware_then_connect_to_new_ap()
diff --git a/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py b/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py
new file mode 100644
index 0000000..15e84ff
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py
@@ -0,0 +1,266 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import nsd_const as nconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ProtocolsTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware data-paths: validating protocols running on
+  top of a data-path"""
+
+    SERVICE_NAME = "GoogleTestServiceXY"
+
+    def run_ping6(self, dut, peer_ipv6):
+        """Run a ping6 over the specified device/link
+
+    Args:
+      dut: Device on which to execute ping6
+      peer_ipv6: Scoped IPv6 address of the peer to ping
+    """
+        cmd = "ping6 -c 3 -W 5 %s" % peer_ipv6
+        results = dut.adb.shell(cmd)
+        self.log.info("cmd='%s' -> '%s'", cmd, results)
+        if results == "":
+            asserts.fail("ping6 empty results - seems like a failure")
+
+    ########################################################################
+
+    @test_tracker_info(uuid="ce103067-7fdd-4379-9a2b-d238959f1d53")
+    def test_ping6_oob(self):
+        """Validate that ping6 works correctly on an NDP created using OOB (out-of
+    band) discovery"""
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        # create NDP
+        (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+         resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+        self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                      resp_aware_if)
+        self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                      resp_ipv6)
+
+        # run ping6
+        self.run_ping6(init_dut, resp_ipv6)
+        self.run_ping6(resp_dut, init_ipv6)
+
+        # clean-up
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    @test_tracker_info(uuid="fef86a48-0e05-464b-8c66-64316275c5ba")
+    def test_ping6_ib_unsolicited_passive(self):
+        """Validate that ping6 works correctly on an NDP created using Aware
+    discovery with UNSOLICITED/PASSIVE sessions."""
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        # create NDP
+        (p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6,
+         s_ipv6) = autils.create_ib_ndp(
+             p_dut,
+             s_dut,
+             p_config=autils.create_discovery_config(
+                 self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+             s_config=autils.create_discovery_config(
+                 self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+             device_startup_offset=self.device_startup_offset)
+        self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+        self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+
+        # run ping6
+        self.run_ping6(p_dut, s_ipv6)
+        self.run_ping6(s_dut, p_ipv6)
+
+        # clean-up
+        p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+    @test_tracker_info(uuid="5bbd68a9-994b-4c26-88cd-43388cec280b")
+    def test_ping6_ib_solicited_active(self):
+        """Validate that ping6 works correctly on an NDP created using Aware
+    discovery with SOLICITED/ACTIVE sessions."""
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        # create NDP
+        (p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6,
+         s_ipv6) = autils.create_ib_ndp(
+             p_dut,
+             s_dut,
+             p_config=autils.create_discovery_config(
+                 self.SERVICE_NAME, aconsts.PUBLISH_TYPE_SOLICITED),
+             s_config=autils.create_discovery_config(
+                 self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_ACTIVE),
+             device_startup_offset=self.device_startup_offset)
+        self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+        self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+
+        # run ping6
+        self.run_ping6(p_dut, s_ipv6)
+        self.run_ping6(s_dut, p_ipv6)
+
+        # clean-up
+        p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+    def test_ping6_oob_max_ndp(self):
+        """Validate that ping6 works correctly on multiple NDPs brought up
+    concurrently. Uses the capability of the device to determine the max
+    number of NDPs to set up.
+
+    Note: the test requires MAX_NDP + 1 devices to be validated. If these are
+    not available the test will fail."""
+        dut = self.android_devices[0]
+
+        # get max NDP: using first available device (assumes all devices are the
+        # same)
+        max_ndp = dut.aware_capabilities[aconsts.CAP_MAX_NDP_SESSIONS]
+        asserts.assert_true(
+            len(self.android_devices) > max_ndp,
+            'Needed %d devices to run the test, have %d' %
+            (max_ndp + 1, len(self.android_devices)))
+
+        # create all NDPs
+        dut_aware_if = None
+        dut_ipv6 = None
+        peers_aware_ifs = []
+        peers_ipv6s = []
+        dut_requests = []
+        peers_requests = []
+        for i in range(max_ndp):
+            (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+             init_ipv6, resp_ipv6) = autils.create_oob_ndp(
+                 dut, self.android_devices[i + 1])
+            self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                          resp_aware_if)
+            self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                          resp_ipv6)
+
+            dut_requests.append(init_req_key)
+            peers_requests.append(resp_req_key)
+            if dut_aware_if is None:
+                dut_aware_if = init_aware_if
+            else:
+                asserts.assert_equal(
+                    dut_aware_if, init_aware_if,
+                    "DUT (Initiator) interface changed on subsequent NDPs!?")
+            if dut_ipv6 is None:
+                dut_ipv6 = init_ipv6
+            else:
+                asserts.assert_equal(
+                    dut_ipv6, init_ipv6,
+                    "DUT (Initiator) IPv6 changed on subsequent NDPs!?")
+            peers_aware_ifs.append(resp_aware_if)
+            peers_ipv6s.append(resp_ipv6)
+
+        # run ping6
+        for i in range(max_ndp):
+            self.run_ping6(dut, peers_ipv6s[i])
+            self.run_ping6(self.android_devices[i + 1], dut_ipv6)
+
+        # cleanup
+        for i in range(max_ndp):
+            dut.droid.connectivityUnregisterNetworkCallback(dut_requests[i])
+            self.android_devices[
+                i + 1].droid.connectivityUnregisterNetworkCallback(
+                    peers_requests[i])
+
+    def test_nsd_oob(self):
+        """Validate that NSD (mDNS) works correctly on an NDP created using OOB
+    (out-of band) discovery"""
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        # create NDP
+        (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+         resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+        self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                      resp_aware_if)
+        self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                      resp_ipv6)
+
+        # run NSD
+        nsd_service_info = {
+            "serviceInfoServiceName": "sl4aTestAwareNsd",
+            "serviceInfoServiceType": "_simple-tx-rx._tcp.",
+            "serviceInfoPort": 2257
+        }
+        nsd_reg = None
+        nsd_discovery = None
+        try:
+            # Initiator registers an NSD service
+            nsd_reg = init_dut.droid.nsdRegisterService(nsd_service_info)
+            event_nsd = autils.wait_for_event_with_keys(
+                init_dut, nconsts.REG_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+                (nconsts.REG_LISTENER_CALLBACK,
+                 nconsts.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED))
+            self.log.info("Initiator %s: %s",
+                          nconsts.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED,
+                          event_nsd["data"])
+
+            # Responder starts an NSD discovery
+            nsd_discovery = resp_dut.droid.nsdDiscoverServices(
+                nsd_service_info[nconsts.NSD_SERVICE_INFO_SERVICE_TYPE])
+            event_nsd = autils.wait_for_event_with_keys(
+                resp_dut, nconsts.DISCOVERY_LISTENER_EVENT,
+                autils.EVENT_TIMEOUT,
+                (nconsts.DISCOVERY_LISTENER_DATA_CALLBACK,
+                 nconsts.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND))
+            self.log.info("Responder %s: %s",
+                          nconsts.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND,
+                          event_nsd["data"])
+
+            # Responder resolves IP address of Initiator from NSD service discovery
+            resp_dut.droid.nsdResolveService(event_nsd["data"])
+            event_nsd = autils.wait_for_event_with_keys(
+                resp_dut, nconsts.RESOLVE_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+                (nconsts.RESOLVE_LISTENER_DATA_CALLBACK,
+                 nconsts.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED))
+            self.log.info("Responder %s: %s",
+                          nconsts.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED,
+                          event_nsd["data"])
+
+            # mDNS returns first character as '/' - strip
+            # out to get clean IPv6
+            init_ipv6_nsd = event_nsd["data"][nconsts.NSD_SERVICE_INFO_HOST][
+                1:]
+
+            # mDNS doesn't seem to return a scoped host so strip it out of the
+            # local result (for now)
+            scope_index = init_ipv6.find("%")
+            if scope_index != -1:
+                init_ipv6 = init_ipv6[:scope_index]
+
+            asserts.assert_equal(
+                init_ipv6, init_ipv6_nsd,
+                "Initiator's IPv6 address obtained through NSD doesn't match!?"
+            )
+        finally:
+            # Stop NSD
+            if nsd_reg is not None:
+                init_dut.droid.nsdUnregisterService(nsd_reg)
+            if nsd_discovery is not None:
+                resp_dut.droid.nsdStopServiceDiscovery(nsd_discovery)
+
+        # clean-up
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
diff --git a/acts_tests/tests/google/wifi/aware/functional/functional b/acts_tests/tests/google/wifi/aware/functional/functional
new file mode 100644
index 0000000..e13a470
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/functional/functional
@@ -0,0 +1,9 @@
+AttachTest
+DiscoveryTest
+MessageTest
+DataPathTest
+MacRandomTest
+CapabilitiesTest
+ProtocolsTest
+NonConcurrencyTest
+MatchFilterTest
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py b/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py
new file mode 100644
index 0000000..e29cd71
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py
@@ -0,0 +1,98 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ServiceIdsTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware to verify that beacons include service IDs
+  for discovery.
+
+  Note: this test is an OTA (over-the-air) and requires a Sniffer.
+  """
+
+    def start_discovery_session(self, dut, session_id, is_publish, dtype,
+                                service_name):
+        """Start a discovery session
+
+    Args:
+      dut: Device under test
+      session_id: ID of the Aware session in which to start discovery
+      is_publish: True for a publish session, False for subscribe session
+      dtype: Type of the discovery session
+      service_name: Service name to use for the discovery session
+
+    Returns:
+      Discovery session ID.
+    """
+        config = {}
+        config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+        config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+
+        if is_publish:
+            disc_id = dut.droid.wifiAwarePublish(session_id, config)
+            event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+        else:
+            disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+            event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+        autils.wait_for_event(dut, event_name)
+        return disc_id
+
+    ####################################################################
+
+    def test_service_ids_in_beacon(self):
+        """Verify that beacons include service IDs for both publish and subscribe
+    sessions of all types: solicited/unsolicited/active/passive."""
+        dut = self.android_devices[0]
+
+        self.log.info("Reminder: start a sniffer before running test")
+
+        # attach
+        session_id = dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+        ident_event = autils.wait_for_event(
+            dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        mac = ident_event["data"]["mac"]
+        self.log.info("Source MAC Address of 'interesting' packets = %s", mac)
+        self.log.info("Wireshark filter = 'wlan.ta == %s:%s:%s:%s:%s:%s'",
+                      mac[0:2], mac[2:4], mac[4:6], mac[6:8], mac[8:10],
+                      mac[10:12])
+
+        time.sleep(5)  # get some samples pre-discovery
+
+        # start 4 discovery session (one of each type)
+        self.start_discovery_session(dut, session_id, True,
+                                     aconsts.PUBLISH_TYPE_UNSOLICITED,
+                                     "GoogleTestService-Pub-Unsolicited")
+        self.start_discovery_session(dut, session_id, True,
+                                     aconsts.PUBLISH_TYPE_SOLICITED,
+                                     "GoogleTestService-Pub-Solicited")
+        self.start_discovery_session(dut, session_id, False,
+                                     aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                                     "GoogleTestService-Sub-Active")
+        self.start_discovery_session(dut, session_id, False,
+                                     aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                                     "GoogleTestService-Sub-Passive")
+
+        time.sleep(15)  # get some samples while discovery is alive
+
+        self.log.info("Reminder: stop sniffer")
diff --git a/acts_tests/tests/google/wifi/aware/ota/ota b/acts_tests/tests/google/wifi/aware/ota/ota
new file mode 100644
index 0000000..27f6724
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/ota/ota
@@ -0,0 +1 @@
+ServiceIdsTest
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py b/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
new file mode 100644
index 0000000..8ebff89
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
@@ -0,0 +1,825 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class LatencyTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
+    SERVICE_NAME = "GoogleTestServiceXY"
+
+    # number of second to 'reasonably' wait to make sure that devices synchronize
+    # with each other - useful for OOB test cases, where the OOB discovery would
+    # take some time
+    WAIT_FOR_CLUSTER = 5
+
+    def start_discovery_session(self, dut, session_id, is_publish, dtype):
+        """Start a discovery session
+
+    Args:
+      dut: Device under test
+      session_id: ID of the Aware session in which to start discovery
+      is_publish: True for a publish session, False for subscribe session
+      dtype: Type of the discovery session
+
+    Returns:
+      Discovery session started event.
+    """
+        config = {}
+        config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+        config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceXY"
+
+        if is_publish:
+            disc_id = dut.droid.wifiAwarePublish(session_id, config)
+            event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+        else:
+            disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+            event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+        event = autils.wait_for_event(dut, event_name)
+        return disc_id, event
+
+    def run_synchronization_latency(self, results, do_unsolicited_passive,
+                                    dw_24ghz, dw_5ghz, num_iterations,
+                                    startup_offset, timeout_period):
+        """Run the synchronization latency test with the specified DW intervals.
+    There is no direct measure of synchronization. Instead starts a discovery
+    session as soon as possible and measures both probability of discovery
+    within a timeout period and the actual discovery time (not necessarily
+    accurate).
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      do_unsolicited_passive: True for unsolicited/passive, False for
+                              solicited/active.
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+      startup_offset: The start-up gap (in seconds) between the two devices
+      timeout_period: Time period over which to measure synchronization
+    """
+        key = "%s_dw24_%d_dw5_%d_offset_%d" % ("unsolicited_passive"
+                                               if do_unsolicited_passive else
+                                               "solicited_active", dw_24ghz,
+                                               dw_5ghz, startup_offset)
+        results[key] = {}
+        results[key]["num_iterations"] = num_iterations
+
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # override the default DW configuration
+        autils.config_power_settings(p_dut, dw_24ghz, dw_5ghz)
+        autils.config_power_settings(s_dut, dw_24ghz, dw_5ghz)
+
+        latencies = []
+        failed_discoveries = 0
+        for i in range(num_iterations):
+            # Publisher+Subscriber: attach and wait for confirmation
+            p_id = p_dut.droid.wifiAwareAttach(False)
+            autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+            time.sleep(startup_offset)
+            s_id = s_dut.droid.wifiAwareAttach(False)
+            autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+            # start publish
+            p_disc_id, p_disc_event = self.start_discovery_session(
+                p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+                if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED)
+
+            # start subscribe
+            s_disc_id, s_session_event = self.start_discovery_session(
+                s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+                if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+            # wait for discovery (allow for failures here since running lots of
+            # samples and would like to get the partial data even in the presence of
+            # errors)
+            try:
+                discovery_event = s_dut.ed.pop_event(
+                    aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, timeout_period)
+                s_dut.log.info(
+                    "[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s",
+                    discovery_event["data"])
+            except queue.Empty:
+                s_dut.log.info("[Subscriber] Timed out while waiting for "
+                               "SESSION_CB_ON_SERVICE_DISCOVERED")
+                failed_discoveries = failed_discoveries + 1
+                continue
+            finally:
+                # destroy sessions
+                p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+                s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+                p_dut.droid.wifiAwareDestroy(p_id)
+                s_dut.droid.wifiAwareDestroy(s_id)
+
+            # collect latency information
+            latencies.append(
+                discovery_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS] -
+                s_session_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS])
+            self.log.info("Latency #%d = %d" % (i, latencies[-1]))
+
+        autils.extract_stats(
+            s_dut,
+            data=latencies,
+            results=results[key],
+            key_prefix="",
+            log_prefix="Subscribe Session Sync/Discovery (%s, dw24=%d, dw5=%d)"
+            % ("Unsolicited/Passive" if do_unsolicited_passive else
+               "Solicited/Active", dw_24ghz, dw_5ghz))
+        results[key]["num_failed_discovery"] = failed_discoveries
+
+    def run_discovery_latency(self, results, do_unsolicited_passive, dw_24ghz,
+                              dw_5ghz, num_iterations):
+        """Run the service discovery latency test with the specified DW intervals.
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      do_unsolicited_passive: True for unsolicited/passive, False for
+                              solicited/active.
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+    """
+        key = "%s_dw24_%d_dw5_%d" % ("unsolicited_passive"
+                                     if do_unsolicited_passive else
+                                     "solicited_active", dw_24ghz, dw_5ghz)
+        results[key] = {}
+        results[key]["num_iterations"] = num_iterations
+
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # override the default DW configuration
+        autils.config_power_settings(p_dut, dw_24ghz, dw_5ghz)
+        autils.config_power_settings(s_dut, dw_24ghz, dw_5ghz)
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # start publish
+        p_disc_event = self.start_discovery_session(
+            p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+            if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED)
+
+        # wait for for devices to synchronize with each other - used so that first
+        # discovery isn't biased by synchronization.
+        time.sleep(self.WAIT_FOR_CLUSTER)
+
+        # loop, perform discovery, and collect latency information
+        latencies = []
+        failed_discoveries = 0
+        for i in range(num_iterations):
+            # start subscribe
+            s_disc_id, s_session_event = self.start_discovery_session(
+                s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+                if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+            # wait for discovery (allow for failures here since running lots of
+            # samples and would like to get the partial data even in the presence of
+            # errors)
+            try:
+                discovery_event = s_dut.ed.pop_event(
+                    aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                    autils.EVENT_TIMEOUT)
+            except queue.Empty:
+                s_dut.log.info("[Subscriber] Timed out while waiting for "
+                               "SESSION_CB_ON_SERVICE_DISCOVERED")
+                failed_discoveries = failed_discoveries + 1
+                continue
+            finally:
+                # destroy subscribe
+                s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+            # collect latency information
+            latencies.append(
+                discovery_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS] -
+                s_session_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS])
+            self.log.info("Latency #%d = %d" % (i, latencies[-1]))
+
+        autils.extract_stats(
+            s_dut,
+            data=latencies,
+            results=results[key],
+            key_prefix="",
+            log_prefix="Subscribe Session Discovery (%s, dw24=%d, dw5=%d)" %
+            ("Unsolicited/Passive" if do_unsolicited_passive else
+             "Solicited/Active", dw_24ghz, dw_5ghz))
+        results[key]["num_failed_discovery"] = failed_discoveries
+
+        # clean up
+        p_dut.droid.wifiAwareDestroyAll()
+        s_dut.droid.wifiAwareDestroyAll()
+
+    def run_message_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+        """Run the message tx latency test with the specified DW intervals.
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+    """
+        key = "dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+        results[key] = {}
+        results[key]["num_iterations"] = num_iterations
+
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        # override the default DW configuration
+        autils.config_power_settings(p_dut, dw_24ghz, dw_5ghz)
+        autils.config_power_settings(s_dut, dw_24ghz, dw_5ghz)
+
+        # Start up a discovery session
+        (p_id, s_id, p_disc_id, s_disc_id,
+         peer_id_on_sub) = autils.create_discovery_pair(
+             p_dut,
+             s_dut,
+             p_config=autils.create_discovery_config(
+                 self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+             s_config=autils.create_discovery_config(
+                 self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+             device_startup_offset=self.device_startup_offset)
+
+        latencies = []
+        failed_tx = 0
+        messages_rx = 0
+        missing_rx = 0
+        corrupted_rx = 0
+        for i in range(num_iterations):
+            # send message
+            msg_s2p = "Message Subscriber -> Publisher #%d" % i
+            next_msg_id = self.get_next_msg_id()
+            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                             next_msg_id, msg_s2p, 0)
+
+            # wait for Tx confirmation
+            try:
+                sub_tx_msg_event = s_dut.ed.pop_event(
+                    aconsts.SESSION_CB_ON_MESSAGE_SENT,
+                    2 * autils.EVENT_TIMEOUT)
+                latencies.append(sub_tx_msg_event["data"][
+                    aconsts.SESSION_CB_KEY_LATENCY_MS])
+            except queue.Empty:
+                s_dut.log.info("[Subscriber] Timed out while waiting for "
+                               "SESSION_CB_ON_MESSAGE_SENT")
+                failed_tx = failed_tx + 1
+                continue
+
+            # wait for Rx confirmation (and validate contents)
+            try:
+                pub_rx_msg_event = p_dut.ed.pop_event(
+                    aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                    2 * autils.EVENT_TIMEOUT)
+                messages_rx = messages_rx + 1
+                if (pub_rx_msg_event["data"]
+                    [aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING] != msg_s2p):
+                    corrupted_rx = corrupted_rx + 1
+            except queue.Empty:
+                s_dut.log.info("[Publisher] Timed out while waiting for "
+                               "SESSION_CB_ON_MESSAGE_RECEIVED")
+                missing_rx = missing_rx + 1
+                continue
+
+        autils.extract_stats(
+            s_dut,
+            data=latencies,
+            results=results[key],
+            key_prefix="",
+            log_prefix="Subscribe Session Discovery (dw24=%d, dw5=%d)" %
+            (dw_24ghz, dw_5ghz))
+        results[key]["failed_tx"] = failed_tx
+        results[key]["messages_rx"] = messages_rx
+        results[key]["missing_rx"] = missing_rx
+        results[key]["corrupted_rx"] = corrupted_rx
+
+        # clean up
+        p_dut.droid.wifiAwareDestroyAll()
+        s_dut.droid.wifiAwareDestroyAll()
+
+    def run_ndp_oob_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+        """Runs the NDP setup with OOB (out-of-band) discovery latency test.
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+    """
+        key_avail = "on_avail_dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+        key_link_props = "link_props_dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+        results[key_avail] = {}
+        results[key_link_props] = {}
+        results[key_avail]["num_iterations"] = num_iterations
+
+        init_dut = self.android_devices[0]
+        init_dut.pretty_name = 'Initiator'
+        resp_dut = self.android_devices[1]
+        resp_dut.pretty_name = 'Responder'
+
+        # override the default DW configuration
+        autils.config_power_settings(init_dut, dw_24ghz, dw_5ghz)
+        autils.config_power_settings(resp_dut, dw_24ghz, dw_5ghz)
+
+        # Initiator+Responder: attach and wait for confirmation & identity
+        init_id = init_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        init_ident_event = autils.wait_for_event(
+            init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        init_mac = init_ident_event['data']['mac']
+        time.sleep(self.device_startup_offset)
+        resp_id = resp_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        resp_ident_event = autils.wait_for_event(
+            resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        resp_mac = resp_ident_event['data']['mac']
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        on_available_latencies = []
+        link_props_latencies = []
+        ndp_setup_failures = 0
+        for i in range(num_iterations):
+            # Responder: request network
+            resp_req_key = autils.request_network(
+                resp_dut,
+                resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                    resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+            # Initiator: request network
+            init_req_key = autils.request_network(
+                init_dut,
+                init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                    init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+            # Initiator & Responder: wait for network formation
+            got_on_available = False
+            got_on_link_props = False
+            while not got_on_available or not got_on_link_props:
+                try:
+                    nc_event = init_dut.ed.pop_event(
+                        cconsts.EVENT_NETWORK_CALLBACK,
+                        autils.EVENT_NDP_TIMEOUT)
+                    if nc_event["data"][
+                            cconsts.
+                            NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+                        got_on_available = True
+                        on_available_latencies.append(
+                            nc_event["data"][cconsts.NETWORK_CB_KEY_CURRENT_TS]
+                            -
+                            nc_event["data"][cconsts.NETWORK_CB_KEY_CREATE_TS])
+                    elif (nc_event["data"][cconsts.NETWORK_CB_KEY_EVENT] ==
+                          cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+                        got_on_link_props = True
+                        link_props_latencies.append(
+                            nc_event["data"][cconsts.NETWORK_CB_KEY_CURRENT_TS]
+                            -
+                            nc_event["data"][cconsts.NETWORK_CB_KEY_CREATE_TS])
+                except queue.Empty:
+                    ndp_setup_failures = ndp_setup_failures + 1
+                    init_dut.log.info(
+                        "[Initiator] Timed out while waiting for "
+                        "EVENT_NETWORK_CALLBACK")
+                    break
+
+            # clean-up
+            init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+            resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+
+            # wait to make sure previous NDP terminated, otherwise its termination
+            # time will be counted in the setup latency!
+            time.sleep(2)
+
+        autils.extract_stats(
+            init_dut,
+            data=on_available_latencies,
+            results=results[key_avail],
+            key_prefix="",
+            log_prefix="NDP setup OnAvailable(dw24=%d, dw5=%d)" % (dw_24ghz,
+                                                                   dw_5ghz))
+        autils.extract_stats(
+            init_dut,
+            data=link_props_latencies,
+            results=results[key_link_props],
+            key_prefix="",
+            log_prefix="NDP setup OnLinkProperties (dw24=%d, dw5=%d)" %
+            (dw_24ghz, dw_5ghz))
+        results[key_avail]["ndp_setup_failures"] = ndp_setup_failures
+
+    def run_end_to_end_latency(self, results, dw_24ghz, dw_5ghz,
+                               num_iterations, startup_offset, include_setup):
+        """Measure the latency for end-to-end communication link setup:
+    - Start Aware
+    - Discovery
+    - Message from Sub -> Pub
+    - Message from Pub -> Sub
+    - NDP setup
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+      startup_offset: The start-up gap (in seconds) between the two devices
+      include_setup: True to include the cluster setup in the latency
+                    measurements.
+    """
+        key = "dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+        results[key] = {}
+        results[key]["num_iterations"] = num_iterations
+
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # override the default DW configuration
+        autils.config_power_settings(p_dut, dw_24ghz, dw_5ghz)
+        autils.config_power_settings(s_dut, dw_24ghz, dw_5ghz)
+
+        latencies = []
+
+        # allow for failures here since running lots of samples and would like to
+        # get the partial data even in the presence of errors
+        failures = 0
+
+        if not include_setup:
+            # Publisher+Subscriber: attach and wait for confirmation
+            p_id = p_dut.droid.wifiAwareAttach(False)
+            autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+            time.sleep(startup_offset)
+            s_id = s_dut.droid.wifiAwareAttach(False)
+            autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        for i in range(num_iterations):
+            while (True):  # for pseudo-goto/finalize
+                timestamp_start = time.perf_counter()
+
+                if include_setup:
+                    # Publisher+Subscriber: attach and wait for confirmation
+                    p_id = p_dut.droid.wifiAwareAttach(False)
+                    autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+                    time.sleep(startup_offset)
+                    s_id = s_dut.droid.wifiAwareAttach(False)
+                    autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+                # start publish
+                p_disc_id, p_disc_event = self.start_discovery_session(
+                    p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED)
+
+                # start subscribe
+                s_disc_id, s_session_event = self.start_discovery_session(
+                    s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE)
+
+                # wait for discovery (allow for failures here since running lots of
+                # samples and would like to get the partial data even in the presence of
+                # errors)
+                try:
+                    event = s_dut.ed.pop_event(
+                        aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                        autils.EVENT_TIMEOUT)
+                    s_dut.log.info(
+                        "[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s",
+                        event["data"])
+                    peer_id_on_sub = event['data'][
+                        aconsts.SESSION_CB_KEY_PEER_ID]
+                except queue.Empty:
+                    s_dut.log.info("[Subscriber] Timed out while waiting for "
+                                   "SESSION_CB_ON_SERVICE_DISCOVERED")
+                    failures = failures + 1
+                    break
+
+                # message from Sub -> Pub
+                msg_s2p = "Message Subscriber -> Publisher #%d" % i
+                next_msg_id = self.get_next_msg_id()
+                s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                                 next_msg_id, msg_s2p, 0)
+
+                # wait for Tx confirmation
+                try:
+                    s_dut.ed.pop_event(aconsts.SESSION_CB_ON_MESSAGE_SENT,
+                                       autils.EVENT_TIMEOUT)
+                except queue.Empty:
+                    s_dut.log.info("[Subscriber] Timed out while waiting for "
+                                   "SESSION_CB_ON_MESSAGE_SENT")
+                    failures = failures + 1
+                    break
+
+                # wait for Rx confirmation (and validate contents)
+                try:
+                    event = p_dut.ed.pop_event(
+                        aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                        autils.EVENT_TIMEOUT)
+                    peer_id_on_pub = event['data'][
+                        aconsts.SESSION_CB_KEY_PEER_ID]
+                    if (event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+                            != msg_s2p):
+                        p_dut.log.info(
+                            "[Publisher] Corrupted input message - %s", event)
+                        failures = failures + 1
+                        break
+                except queue.Empty:
+                    p_dut.log.info("[Publisher] Timed out while waiting for "
+                                   "SESSION_CB_ON_MESSAGE_RECEIVED")
+                    failures = failures + 1
+                    break
+
+                # message from Pub -> Sub
+                msg_p2s = "Message Publisher -> Subscriber #%d" % i
+                next_msg_id = self.get_next_msg_id()
+                p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
+                                                 next_msg_id, msg_p2s, 0)
+
+                # wait for Tx confirmation
+                try:
+                    p_dut.ed.pop_event(aconsts.SESSION_CB_ON_MESSAGE_SENT,
+                                       autils.EVENT_TIMEOUT)
+                except queue.Empty:
+                    p_dut.log.info("[Publisher] Timed out while waiting for "
+                                   "SESSION_CB_ON_MESSAGE_SENT")
+                    failures = failures + 1
+                    break
+
+                # wait for Rx confirmation (and validate contents)
+                try:
+                    event = s_dut.ed.pop_event(
+                        aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                        autils.EVENT_TIMEOUT)
+                    if (event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+                            != msg_p2s):
+                        s_dut.log.info(
+                            "[Subscriber] Corrupted input message - %s", event)
+                        failures = failures + 1
+                        break
+                except queue.Empty:
+                    s_dut.log.info("[Subscriber] Timed out while waiting for "
+                                   "SESSION_CB_ON_MESSAGE_RECEIVED")
+                    failures = failures + 1
+                    break
+
+                # create NDP
+
+                # Publisher: request network
+                p_req_key = autils.request_network(
+                    p_dut,
+                    p_dut.droid.wifiAwareCreateNetworkSpecifier(
+                        p_disc_id, peer_id_on_pub, None))
+
+                # Subscriber: request network
+                s_req_key = autils.request_network(
+                    s_dut,
+                    s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                        s_disc_id, peer_id_on_sub, None))
+
+                # Publisher & Subscriber: wait for network formation
+                try:
+                    p_net_event = autils.wait_for_event_with_keys(
+                        p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                        autils.EVENT_TIMEOUT,
+                        (cconsts.NETWORK_CB_KEY_EVENT,
+                         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+                    s_net_event = autils.wait_for_event_with_keys(
+                        s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                        autils.EVENT_TIMEOUT,
+                        (cconsts.NETWORK_CB_KEY_EVENT,
+                         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+                except:
+                    failures = failures + 1
+                    break
+
+                p_aware_if = p_net_event["data"][
+                    cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+                s_aware_if = s_net_event["data"][
+                    cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+                p_ipv6 = \
+                p_dut.droid.connectivityGetLinkLocalIpv6Address(p_aware_if).split("%")[
+                  0]
+                s_ipv6 = \
+                s_dut.droid.connectivityGetLinkLocalIpv6Address(s_aware_if).split("%")[
+                  0]
+
+                p_dut.log.info("[Publisher] IF=%s, IPv6=%s", p_aware_if,
+                               p_ipv6)
+                s_dut.log.info("[Subscriber] IF=%s, IPv6=%s", s_aware_if,
+                               s_ipv6)
+
+                latencies.append(time.perf_counter() - timestamp_start)
+                break
+
+            # destroy sessions
+            p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+            s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+            if include_setup:
+                p_dut.droid.wifiAwareDestroy(p_id)
+                s_dut.droid.wifiAwareDestroy(s_id)
+
+        autils.extract_stats(
+            p_dut,
+            data=latencies,
+            results=results[key],
+            key_prefix="",
+            log_prefix="End-to-End(dw24=%d, dw5=%d)" % (dw_24ghz, dw_5ghz))
+        results[key]["failures"] = failures
+
+    ########################################################################
+
+    def test_synchronization_default_dws(self):
+        """Measure the device synchronization for default dws. Loop over values
+    from 0 to 4 seconds."""
+        results = {}
+        for startup_offset in range(5):
+            self.run_synchronization_latency(
+                results=results,
+                do_unsolicited_passive=True,
+                dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+                dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+                num_iterations=10,
+                startup_offset=startup_offset,
+                timeout_period=20)
+        asserts.explicit_pass(
+            "test_synchronization_default_dws finished", extras=results)
+
+    def test_synchronization_non_interactive_dws(self):
+        """Measure the device synchronization for non-interactive dws. Loop over
+    values from 0 to 4 seconds."""
+        results = {}
+        for startup_offset in range(5):
+            self.run_synchronization_latency(
+                results=results,
+                do_unsolicited_passive=True,
+                dw_24ghz=aconsts.POWER_DW_24_NON_INTERACTIVE,
+                dw_5ghz=aconsts.POWER_DW_5_NON_INTERACTIVE,
+                num_iterations=10,
+                startup_offset=startup_offset,
+                timeout_period=20)
+        asserts.explicit_pass(
+            "test_synchronization_non_interactive_dws finished",
+            extras=results)
+
+    def test_discovery_latency_default_dws(self):
+        """Measure the service discovery latency with the default DW configuration.
+    """
+        results = {}
+        self.run_discovery_latency(
+            results=results,
+            do_unsolicited_passive=True,
+            dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+            num_iterations=100)
+        asserts.explicit_pass(
+            "test_discovery_latency_default_parameters finished",
+            extras=results)
+
+    def test_discovery_latency_non_interactive_dws(self):
+        """Measure the service discovery latency with the DW configuration for non
+    -interactive mode (lower power)."""
+        results = {}
+        self.run_discovery_latency(
+            results=results,
+            do_unsolicited_passive=True,
+            dw_24ghz=aconsts.POWER_DW_24_NON_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_NON_INTERACTIVE,
+            num_iterations=100)
+        asserts.explicit_pass(
+            "test_discovery_latency_non_interactive_dws finished",
+            extras=results)
+
+    def test_discovery_latency_all_dws(self):
+        """Measure the service discovery latency with all DW combinations (low
+    iteration count)"""
+        results = {}
+        for dw24 in range(1, 6):  # permitted values: 1-5
+            for dw5 in range(0, 6):  # permitted values: 0, 1-5
+                self.run_discovery_latency(
+                    results=results,
+                    do_unsolicited_passive=True,
+                    dw_24ghz=dw24,
+                    dw_5ghz=dw5,
+                    num_iterations=10)
+        asserts.explicit_pass(
+            "test_discovery_latency_all_dws finished", extras=results)
+
+    def test_message_latency_default_dws(self):
+        """Measure the send message latency with the default DW configuration. Test
+    performed on non-queued message transmission - i.e. waiting for confirmation
+    of reception (ACK) before sending the next message."""
+        results = {}
+        self.run_message_latency(
+            results=results,
+            dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+            num_iterations=100)
+        asserts.explicit_pass(
+            "test_message_latency_default_dws finished", extras=results)
+
+    def test_message_latency_non_interactive_dws(self):
+        """Measure the send message latency with the DW configuration for
+    non-interactive mode. Test performed on non-queued message transmission -
+    i.e. waiting for confirmation of reception (ACK) before sending the next
+    message."""
+        results = {}
+        self.run_message_latency(
+            results=results,
+            dw_24ghz=aconsts.POWER_DW_24_NON_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_NON_INTERACTIVE,
+            num_iterations=100)
+        asserts.explicit_pass(
+            "test_message_latency_non_interactive_dws finished",
+            extras=results)
+
+    def test_oob_ndp_setup_latency_default_dws(self):
+        """Measure the NDP setup latency with the default DW configuration. The
+    NDP is setup with OOB (out-of-band) configuration."""
+        results = {}
+        self.run_ndp_oob_latency(
+            results=results,
+            dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+            num_iterations=100)
+        asserts.explicit_pass(
+            "test_ndp_setup_latency_default_dws finished", extras=results)
+
+    def test_oob_ndp_setup_latency_non_interactive_dws(self):
+        """Measure the NDP setup latency with the DW configuration for
+    non-interactive mode. The NDP is setup with OOB (out-of-band)
+    configuration"""
+        results = {}
+        self.run_ndp_oob_latency(
+            results=results,
+            dw_24ghz=aconsts.POWER_DW_24_NON_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_NON_INTERACTIVE,
+            num_iterations=100)
+        asserts.explicit_pass(
+            "test_ndp_setup_latency_non_interactive_dws finished",
+            extras=results)
+
+    def test_end_to_end_latency_default_dws(self):
+        """Measure the latency for end-to-end communication link setup:
+      - Start Aware
+      - Discovery
+      - Message from Sub -> Pub
+      - Message from Pub -> Sub
+      - NDP setup
+    """
+        results = {}
+        self.run_end_to_end_latency(
+            results,
+            dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+            num_iterations=10,
+            startup_offset=0,
+            include_setup=True)
+        asserts.explicit_pass(
+            "test_end_to_end_latency_default_dws finished", extras=results)
+
+    def test_end_to_end_latency_post_attach_default_dws(self):
+        """Measure the latency for end-to-end communication link setup without
+    the initial synchronization:
+      - Start Aware & synchronize initially
+      - Loop:
+        - Discovery
+        - Message from Sub -> Pub
+        - Message from Pub -> Sub
+        - NDP setup
+    """
+        results = {}
+        self.run_end_to_end_latency(
+            results,
+            dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+            dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+            num_iterations=10,
+            startup_offset=0,
+            include_setup=False)
+        asserts.explicit_pass(
+            "test_end_to_end_latency_post_attach_default_dws finished",
+            extras=results)
diff --git a/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py b/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py
new file mode 100644
index 0000000..2dab276
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py
@@ -0,0 +1,440 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import json
+import pprint
+import queue
+import threading
+import time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ThroughputTest(AwareBaseTest):
+    """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
+
+    SERVICE_NAME = "GoogleTestServiceXYZ"
+
+    PASSPHRASE = "This is some random passphrase - very very secure!!"
+    PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
+
+    def request_network(self, dut, ns):
+        """Request a Wi-Fi Aware network.
+
+    Args:
+      dut: Device
+      ns: Network specifier
+    Returns: the request key
+    """
+        network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+        return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+    def run_iperf_single_ndp_aware_only(self, use_ib, results):
+        """Measure iperf performance on a single NDP, with Aware enabled and no
+    infrastructure connection - i.e. device is not associated to an AP.
+
+    Args:
+      use_ib: True to use in-band discovery, False to use out-of-band discovery.
+      results: Dictionary into which to place test results.
+    """
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        if use_ib:
+            # note: Publisher = Responder, Subscribe = Initiator
+            (resp_req_key, init_req_key, resp_aware_if, init_aware_if,
+             resp_ipv6, init_ipv6) = autils.create_ib_ndp(
+                 resp_dut, init_dut,
+                 autils.create_discovery_config(
+                     self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+                 autils.create_discovery_config(
+                     self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                 self.device_startup_offset)
+        else:
+            (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+             init_ipv6, resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+        self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                      resp_aware_if)
+        self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                      resp_ipv6)
+
+        # Run iperf3
+        result, data = init_dut.run_iperf_server("-D")
+        asserts.assert_true(result, "Can't start iperf3 server")
+
+        result, data = resp_dut.run_iperf_client("%s" % init_ipv6, "-6 -J")
+        self.log.debug(data)
+        asserts.assert_true(result,
+                            "Failure starting/running iperf3 in client mode")
+        self.log.debug(pprint.pformat(data))
+
+        # clean-up
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+        # Collect results
+        data_json = json.loads("".join(data))
+        if "error" in data_json:
+            asserts.fail(
+                "iperf run failed: %s" % data_json["error"], extras=data_json)
+        results["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
+        results["rx_rate"] = data_json["end"]["sum_received"][
+            "bits_per_second"]
+        self.log.info("iPerf3: Sent = %d bps Received = %d bps",
+                      results["tx_rate"], results["rx_rate"])
+
+    def run_iperf(self, q, dut, peer_dut, peer_aware_if, dut_ipv6, port):
+        """Runs iperf and places results in the queue.
+
+    Args:
+      q: The queue into which to place the results
+      dut: The DUT on which to run the iperf server command.
+      peer_dut: The DUT on which to run the iperf client command.
+      peer_aware_if: The interface on the DUT.
+      dut_ipv6: The IPv6 address of the server.
+      port: The port to use for the server and client.
+    """
+        result, data = dut.run_iperf_server("-D -p %d" % port)
+        asserts.assert_true(result, "Can't start iperf3 server")
+
+        result, data = peer_dut.run_iperf_client("%s" % dut_ipv6,
+                                                 "-6 -J -p %d" % port)
+        self.log.debug(data)
+        q.put((result, data))
+
+    def run_iperf_max_ndp_aware_only(self, results):
+        """Measure iperf performance on the max number of concurrent OOB NDPs, with
+    Aware enabled and no infrastructure connection - i.e. device is not
+    associated to an AP.
+
+    Note: the test requires MAX_NDP + 1 devices to be validated. If these are
+    not available the test will fail.
+
+    Args:
+      results: Dictionary into which to place test results.
+    """
+        dut = self.android_devices[0]
+
+        # get max NDP: using first available device (assumes all devices are the
+        # same)
+        max_ndp = dut.aware_capabilities[aconsts.CAP_MAX_NDP_SESSIONS]
+        asserts.assert_true(
+            len(self.android_devices) > max_ndp,
+            'Needed %d devices to run the test, have %d' %
+            (max_ndp + 1, len(self.android_devices)))
+
+        # create all NDPs
+        dut_aware_if = None
+        dut_ipv6 = None
+        peers_aware_ifs = []
+        peers_ipv6s = []
+        dut_requests = []
+        peers_requests = []
+        for i in range(max_ndp):
+            (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+             init_ipv6, resp_ipv6) = autils.create_oob_ndp(
+                 dut, self.android_devices[i + 1])
+            self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                          resp_aware_if)
+            self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                          resp_ipv6)
+
+            dut_requests.append(init_req_key)
+            peers_requests.append(resp_req_key)
+            if dut_aware_if is None:
+                dut_aware_if = init_aware_if
+            else:
+                asserts.assert_equal(
+                    dut_aware_if, init_aware_if,
+                    "DUT (Initiator) interface changed on subsequent NDPs!?")
+            if dut_ipv6 is None:
+                dut_ipv6 = init_ipv6
+            else:
+                asserts.assert_equal(
+                    dut_ipv6, init_ipv6,
+                    "DUT (Initiator) IPv6 changed on subsequent NDPs!?")
+            peers_aware_ifs.append(resp_aware_if)
+            peers_ipv6s.append(resp_ipv6)
+
+        # create threads, start them, and wait for all to finish
+        base_port = 5000
+        q = queue.Queue()
+        threads = []
+        for i in range(max_ndp):
+            threads.append(
+                threading.Thread(
+                    target=self.run_iperf,
+                    args=(q, dut, self.android_devices[i + 1],
+                          peers_aware_ifs[i], dut_ipv6, base_port + i)))
+
+        for thread in threads:
+            thread.start()
+
+        for thread in threads:
+            thread.join()
+
+        # cleanup
+        for i in range(max_ndp):
+            dut.droid.connectivityUnregisterNetworkCallback(dut_requests[i])
+            self.android_devices[
+                i + 1].droid.connectivityUnregisterNetworkCallback(
+                    peers_requests[i])
+
+        # collect data
+        for i in range(max_ndp):
+            results[i] = {}
+            result, data = q.get()
+            asserts.assert_true(
+                result, "Failure starting/running iperf3 in client mode")
+            self.log.debug(pprint.pformat(data))
+            data_json = json.loads("".join(data))
+            if "error" in data_json:
+                asserts.fail(
+                    "iperf run failed: %s" % data_json["error"],
+                    extras=data_json)
+            results[i]["tx_rate"] = data_json["end"]["sum_sent"][
+                "bits_per_second"]
+            results[i]["rx_rate"] = data_json["end"]["sum_received"][
+                "bits_per_second"]
+            self.log.info("iPerf3: Sent = %d bps Received = %d bps",
+                          results[i]["tx_rate"], results[i]["rx_rate"])
+
+    ########################################################################
+
+    def test_iperf_single_ndp_aware_only_ib(self):
+        """Measure throughput using iperf on a single NDP, with Aware enabled and
+    no infrastructure connection. Use in-band discovery."""
+        results = {}
+        self.run_iperf_single_ndp_aware_only(use_ib=True, results=results)
+        asserts.explicit_pass(
+            "test_iperf_single_ndp_aware_only_ib passes", extras=results)
+
+    def test_iperf_single_ndp_aware_only_oob(self):
+        """Measure throughput using iperf on a single NDP, with Aware enabled and
+    no infrastructure connection. Use out-of-band discovery."""
+        results = {}
+        self.run_iperf_single_ndp_aware_only(use_ib=False, results=results)
+        asserts.explicit_pass(
+            "test_iperf_single_ndp_aware_only_oob passes", extras=results)
+
+    def test_iperf_max_ndp_aware_only_oob(self):
+        """Measure throughput using iperf on all possible concurrent NDPs, with
+    Aware enabled and no infrastructure connection. Use out-of-band discovery.
+    """
+        results = {}
+        self.run_iperf_max_ndp_aware_only(results=results)
+        asserts.explicit_pass(
+            "test_iperf_max_ndp_aware_only_oob passes", extras=results)
+
+    ########################################################################
+
+    def run_iperf_max_ndi_aware_only(self, sec_configs, results):
+        """Measure iperf performance on multiple NDPs between 2 devices using
+    different security configurations (and hence different NDIs). Test with
+    Aware enabled and no infrastructure connection - i.e. device is not
+    associated to an AP.
+
+    The security configuration can be:
+    - None: open
+    - String: passphrase
+    - otherwise: PMK (byte array)
+
+    Args:
+      sec_configs: list of security configurations
+      results: Dictionary into which to place test results.
+    """
+        init_dut = self.android_devices[0]
+        init_dut.pretty_name = "Initiator"
+        resp_dut = self.android_devices[1]
+        resp_dut.pretty_name = "Responder"
+
+        asserts.skip_if(
+            init_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] <
+            len(sec_configs)
+            or resp_dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] <
+            len(sec_configs),
+            "Initiator or Responder do not support multiple NDIs")
+
+        init_id, init_mac = autils.attach_with_identity(init_dut)
+        resp_id, resp_mac = autils.attach_with_identity(resp_dut)
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        resp_req_keys = []
+        init_req_keys = []
+        resp_aware_ifs = []
+        init_aware_ifs = []
+        resp_aware_ipv6s = []
+        init_aware_ipv6s = []
+
+        for sec in sec_configs:
+            # Responder: request network
+            resp_req_key = autils.request_network(
+                resp_dut,
+                autils.get_network_specifier(resp_dut, resp_id,
+                                             aconsts.DATA_PATH_RESPONDER,
+                                             init_mac, sec))
+            resp_req_keys.append(resp_req_key)
+
+            # Initiator: request network
+            init_req_key = autils.request_network(
+                init_dut,
+                autils.get_network_specifier(init_dut, init_id,
+                                             aconsts.DATA_PATH_INITIATOR,
+                                             resp_mac, sec))
+            init_req_keys.append(init_req_key)
+
+            # Wait for network
+            init_net_event_nc = autils.wait_for_event_with_keys(
+                init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+            resp_net_event_nc = autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+            # validate no leak of information
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in init_net_event_nc[
+                    "data"], "Network specifier leak!")
+            asserts.assert_false(
+                cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in resp_net_event_nc[
+                    "data"], "Network specifier leak!")
+
+            # note that Init <-> Resp since IPv6 are of peer's!
+            resp_ipv6 = init_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+            init_ipv6 = resp_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+            init_net_event_lp = autils.wait_for_event_with_keys(
+                init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+            resp_net_event_lp = autils.wait_for_event_with_keys(
+                resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+                autils.EVENT_NDP_TIMEOUT,
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+            resp_aware_ifs.append(resp_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
+            init_aware_ifs.append(init_net_event_lp["data"][
+                cconsts.NETWORK_CB_KEY_INTERFACE_NAME])
+
+            resp_aware_ipv6s.append(resp_ipv6)
+            init_aware_ipv6s.append(init_ipv6)
+
+        self.log.info("Initiator interfaces/ipv6: %s / %s", init_aware_ifs,
+                      init_aware_ipv6s)
+        self.log.info("Responder interfaces/ipv6: %s / %s", resp_aware_ifs,
+                      resp_aware_ipv6s)
+
+        # create threads, start them, and wait for all to finish
+        base_port = 5000
+        q = queue.Queue()
+        threads = []
+        for i in range(len(sec_configs)):
+            threads.append(
+                threading.Thread(
+                    target=self.run_iperf,
+                    args=(q, init_dut, resp_dut, resp_aware_ifs[i],
+                          init_aware_ipv6s[i], base_port + i)))
+
+        for thread in threads:
+            thread.start()
+
+        for thread in threads:
+            thread.join()
+
+        # release requests
+        for resp_req_key in resp_req_keys:
+            resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+        for init_req_key in init_req_keys:
+            init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+        # collect data
+        for i in range(len(sec_configs)):
+            results[i] = {}
+            result, data = q.get()
+            asserts.assert_true(
+                result, "Failure starting/running iperf3 in client mode")
+            self.log.debug(pprint.pformat(data))
+            data_json = json.loads("".join(data))
+            if "error" in data_json:
+                asserts.fail(
+                    "iperf run failed: %s" % data_json["error"],
+                    extras=data_json)
+            results[i]["tx_rate"] = data_json["end"]["sum_sent"][
+                "bits_per_second"]
+            results[i]["rx_rate"] = data_json["end"]["sum_received"][
+                "bits_per_second"]
+            self.log.info("iPerf3: Sent = %d bps Received = %d bps",
+                          results[i]["tx_rate"], results[i]["rx_rate"])
+
+    def test_iperf_max_ndi_aware_only_passphrases(self):
+        """Test throughput for multiple NDIs configured with different passphrases.
+    """
+        results = {}
+        self.run_iperf_max_ndi_aware_only(
+            [self.PASSPHRASE, self.PASSPHRASE2], results=results)
+        asserts.explicit_pass(
+            "test_iperf_max_ndi_aware_only_passphrases passes", extras=results)
+
+    def run_test_traffic_latency_single_ndp_ib_aware_only_open(self):
+        """Measure IPv6 traffic latency performance(ping) on NDP between 2 devices.
+        Security config is open.
+        """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "subscriber"
+        ndp_info = autils.create_ib_ndp(p_dut,
+                                        s_dut,
+                                        autils.create_discovery_config(
+                                            self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+                                        autils.create_discovery_config(
+                                            self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                                        self.device_startup_offset)
+        p_req_key = ndp_info[0]
+        s_req_key = ndp_info[1]
+        p_aware_if = ndp_info[2]
+        s_aware_if = ndp_info[3]
+        p_ipv6 = ndp_info[4]
+        s_ipv6 = ndp_info[5]
+        self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+        self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+        self.log.info("Start ping %s from %s", s_ipv6, p_ipv6)
+        latency_result = autils.run_ping6(p_dut, s_ipv6)
+        self.log.info("The latency results are %s", latency_result)
+
+    def test_traffic_latency_single_ndp_ib_aware_only_open(self):
+        """Test IPv6 traffic latency performance on NDP with security config is open.
+        """
+        self.run_test_traffic_latency_single_ndp_ib_aware_only_open()
diff --git a/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py b/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
new file mode 100644
index 0000000..b1e71e1
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
@@ -0,0 +1,622 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import json
+import logging
+import os
+from acts import asserts
+from acts import base_test
+from acts import utils
+from acts.controllers import iperf_server as ipf
+from acts.controllers import iperf_client as ipc
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_sniffer
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from WifiRvrTest import WifiRvrTest
+
+AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
+                                          ['ap_settings'])
+
+
+class WifiAwareRvrTest(WifiRvrTest):
+
+    # message ID counter to make sure all uses are unique
+    msg_id = 0
+
+    # offset (in seconds) to separate the start-up of multiple devices.
+    # De-synchronizes the start-up time so that they don't start and stop scanning
+    # at the same time - which can lead to very long clustering times.
+    device_startup_offset = 2
+
+    SERVICE_NAME = "GoogleTestServiceXYZ"
+
+    PASSPHRASE = "This is some random passphrase - very very secure!!"
+    PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
+
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardwares and compiles parameters that are
+        common to all tests in this class.
+        """
+        req_params = [
+            'aware_rvr_test_params', 'testbed_params',
+            'aware_default_power_mode', 'dbs_supported_models'
+        ]
+        opt_params = ['RetailAccessPoints', 'ap_networks', 'OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        if hasattr(self, 'RetailAccessPoints'):
+            self.access_points = retail_ap.create(self.RetailAccessPoints)
+            self.access_point = self.access_points[0]
+        else:
+            self.access_point = AccessPointTuple({})
+        self.testclass_params = self.aware_rvr_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.iperf_server = ipf.create([{
+            'AndroidDevice':
+            self.android_devices[0].serial,
+            'port':
+            '5201'
+        }])[0]
+        self.iperf_client = ipc.create([{
+            'AndroidDevice':
+            self.android_devices[1].serial,
+            'port':
+            '5201'
+        }])[0]
+
+        self.log_path = os.path.join(logging.log_path, 'results')
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+        os.makedirs(self.log_path, exist_ok=True)
+        if not hasattr(self, 'golden_files_list'):
+            if 'golden_results_path' in self.testbed_params:
+                self.golden_files_list = [
+                    os.path.join(self.testbed_params['golden_results_path'],
+                                 file) for file in
+                    os.listdir(self.testbed_params['golden_results_path'])
+                ]
+            else:
+                self.log.warning('No golden files found.')
+                self.golden_files_list = []
+
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        if self.testclass_params.get('airplane_mode', 1):
+            self.log.info('Turning on airplane mode.')
+            for ad in self.android_devices:
+                asserts.assert_true(utils.force_airplane_mode(ad, True),
+                                    "Can not turn on airplane mode.")
+        for ad in self.android_devices:
+            wutils.wifi_toggle_state(ad, True)
+
+    def teardown_class(self):
+        # Turn WiFi OFF
+        for dev in self.android_devices:
+            wutils.wifi_toggle_state(dev, False)
+        self.process_testclass_results()
+        # Teardown AP and release its lockfile
+        self.access_point.teardown()
+
+    def teardown_test(self):
+        self.iperf_server.stop()
+        for ad in self.android_devices:
+            if not ad.droid.doesDeviceSupportWifiAwareFeature():
+                return
+            ad.droid.wifiP2pClose()
+            ad.droid.wifiAwareDestroyAll()
+            autils.reset_device_parameters(ad)
+            autils.validate_forbidden_callbacks(ad)
+            wutils.reset_wifi(ad)
+
+    def setup_aps(self, testcase_params):
+        for network in testcase_params['ap_networks']:
+            self.log.info('Setting AP {} {} interface on channel {}'.format(
+                network['ap_id'], network['interface_id'], network['channel']))
+            self.access_points[network['ap_id']].set_channel(
+                network['interface_id'], network['channel'])
+
+    def setup_duts(self, testcase_params):
+        # Check battery level before test
+        for ad in self.android_devices:
+            if not wputils.health_check(ad, 20):
+                asserts.skip('Overheating or Battery low. Skipping test.')
+            ad.go_to_sleep()
+            wutils.reset_wifi(ad)
+        # Turn screen off to preserve battery
+        for network in testcase_params['ap_networks']:
+            for connected_dut in network['connected_dut']:
+                self.log.info("Connecting DUT {} to {}".format(
+                    connected_dut, self.ap_networks[network['ap_id']][
+                        network['interface_id']]))
+                wutils.wifi_connect(self.android_devices[connected_dut],
+                                    self.ap_networks[network['ap_id']][
+                                        network['interface_id']],
+                                    num_of_tries=5,
+                                    check_connectivity=True)
+
+    def setup_aware_connection(self, testcase_params):
+        # Basic aware setup
+        for ad in self.android_devices:
+            asserts.skip_if(
+                not ad.droid.doesDeviceSupportWifiAwareFeature(),
+                "Device under test does not support Wi-Fi Aware - skipping test"
+            )
+            aware_avail = ad.droid.wifiIsAwareAvailable()
+            ad.droid.wifiP2pClose()
+            wutils.wifi_toggle_state(ad, True)
+            utils.set_location_service(ad, True)
+            if not aware_avail:
+                self.log.info('Aware not available. Waiting ...')
+                autils.wait_for_event(ad,
+                                      aconsts.BROADCAST_WIFI_AWARE_AVAILABLE,
+                                      timeout=30)
+            ad.aware_capabilities = autils.get_aware_capabilities(ad)
+            autils.reset_device_parameters(ad)
+            autils.reset_device_statistics(ad)
+            autils.set_power_mode_parameters(ad, testcase_params['power_mode'])
+            wutils.set_wifi_country_code(ad, wutils.WifiEnums.CountryCode.US)
+            autils.configure_ndp_allow_any_override(ad, True)
+            # set randomization interval to 0 (disable) to reduce likelihood of
+            # interference in tests
+            autils.configure_mac_random_interval(ad, 0)
+            ad.ed.clear_all_events()
+
+        # Establish Aware Connection
+        self.init_dut = self.android_devices[0]
+        self.resp_dut = self.android_devices[1]
+
+        # note: Publisher = Responder, Subscribe = Initiator
+        (resp_req_key, init_req_key, resp_aware_if, init_aware_if, resp_ipv6,
+         init_ipv6) = autils.create_ib_ndp(
+             self.resp_dut, self.init_dut,
+             autils.create_discovery_config(self.SERVICE_NAME,
+                                            aconsts.PUBLISH_TYPE_UNSOLICITED),
+             autils.create_discovery_config(self.SERVICE_NAME,
+                                            aconsts.SUBSCRIBE_TYPE_PASSIVE),
+             self.device_startup_offset)
+        testcase_params['aware_config'] = {
+            "init_req_key": init_req_key,
+            "resp_req_key": resp_req_key,
+            "init_aware_if": init_aware_if,
+            "resp_aware_if": resp_aware_if,
+            "init_ipv6": init_ipv6,
+            "resp_ipv6": resp_ipv6
+        }
+        testcase_params['iperf_server_address'] = init_ipv6
+        for ad in self.android_devices:
+            self.log.warning(
+                ad.adb.shell('cmd wifiaware native_cb get_channel_info'))
+        ndp_config = self.android_devices[0].adb.shell(
+            'cmd wifiaware native_cb get_channel_info')
+        ndp_config = json.loads(ndp_config)
+        ndp_config = ndp_config[list(ndp_config.keys())[0]][0]
+        testcase_params['channel'] = wutils.WifiEnums.freq_to_channel[
+            ndp_config['channelFreq']]
+        if testcase_params['channel'] < 13:
+            testcase_params['mode'] = 'VHT20'
+        else:
+            testcase_params['mode'] = 'VHT80'
+        testcase_params['test_network'] = {'SSID': 'Aware'}
+        self.log.info('Wifi Aware Connection Established on Channel {} {} '
+                      '(Interfaces: {},{})'.format(testcase_params['channel'],
+                                                   testcase_params['mode'],
+                                                   init_aware_if,
+                                                   resp_aware_if))
+
+    def setup_aware_rvr_test(self, testcase_params):
+        # Setup the aps
+        self.setup_aps(testcase_params)
+        # Setup the duts
+        self.setup_duts(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False)
+        # Setup the aware connection
+        self.setup_aware_connection(testcase_params)
+        # Set DUT to monitor RSSI and LLStats on
+        self.monitored_dut = self.android_devices[1]
+
+    def cleanup_aware_rvr_test(self, testcase_params):
+        # clean-up
+        self.resp_dut.droid.connectivityUnregisterNetworkCallback(
+            testcase_params['aware_config']['resp_req_key'])
+        self.init_dut.droid.connectivityUnregisterNetworkCallback(
+            testcase_params['aware_config']['init_req_key'])
+
+    def compile_test_params(self, testcase_params):
+        """Function that completes all test params based on the test name.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Compile RvR parameters
+        num_atten_steps = int((self.testclass_params['atten_stop'] -
+                               self.testclass_params['atten_start']) /
+                              self.testclass_params['atten_step'])
+        testcase_params['atten_range'] = [
+            self.testclass_params['atten_start'] +
+            x * self.testclass_params['atten_step']
+            for x in range(0, num_atten_steps)
+        ]
+
+        # Compile iperf arguments
+        if testcase_params['traffic_type'] == 'TCP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'tcp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'tcp_processes', 1)
+        elif testcase_params['traffic_type'] == 'UDP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'udp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'udp_processes', 1)
+        testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+            duration=self.testclass_params['iperf_duration'],
+            reverse_direction=(testcase_params['traffic_direction'] == 'DL'),
+            socket_size=testcase_params['iperf_socket_size'],
+            num_processes=testcase_params['iperf_processes'],
+            traffic_type=testcase_params['traffic_type'],
+            ipv6=True)
+        testcase_params['use_client_output'] = (
+            testcase_params['traffic_direction'] == 'DL')
+
+        # Compile AP and infrastructure connection parameters
+        ap_networks = []
+        if testcase_params['dut_connected'][0]:
+            band = testcase_params['dut_connected'][0].split('_')[0]
+            ap_networks.append({
+                'ap_id':
+                0,
+                'interface_id':
+                band if band == '2G' else band + '_1',
+                'band':
+                band,
+                'channel':
+                1 if band == '2G' else 36,
+                'connected_dut': [0]
+            })
+
+        if testcase_params['dut_connected'][1]:
+            if testcase_params['dut_connected'][0] == testcase_params[
+                    'dut_connected'][1]:
+                # if connected to same network, add it to the above
+                ap_networks[0]['connected_dut'].append(1)
+            else:
+                band = testcase_params['dut_connected'][1].split('_')[0]
+                if not testcase_params['dut_connected'][0]:
+                    # if it is the only dut connected, assign it to ap 0
+                    ap_id = 0
+                elif band == ap_networks[0]['band']:
+                    # if its connected to same band, connect to ap 1
+                    ap_id = 1
+                else:
+                    # if its on a different band, connect to ap 0 as well
+                    ap_id = 1
+                ap_networks.append({
+                    'ap_id':
+                    ap_id,
+                    'interface_id':
+                    band if band == '2G' else band + '_1',
+                    'band':
+                    band,
+                    'channel':
+                    11 if band == '2G' else 149,
+                    'connected_dut': [1]
+                })
+        testcase_params['ap_networks'] = ap_networks
+
+        return testcase_params
+
+    def _test_aware_rvr(self, testcase_params):
+        """ Function that gets called for each test case
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+
+        # Prepare devices and run test
+        self.setup_aware_rvr_test(testcase_params)
+        rvr_result = self.run_rvr_test(testcase_params)
+        self.cleanup_aware_rvr_test(testcase_params)
+
+        # Post-process results
+        self.testclass_results.append(rvr_result)
+        self.process_test_results(rvr_result)
+        self.pass_fail_check(rvr_result)
+
+
+class WifiAwareRvr_TCP_Test(WifiAwareRvrTest):
+    #Test cases
+    def test_aware_rvr_TCP_DL_ib_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=[False, False],
+        )
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_2'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_DL_ib_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_2'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=[False, False],
+        )
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_2'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_TCP_UL_ib_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_2'])
+        self._test_aware_rvr(testcase_params)
+
+
+class WifiAwareRvr_UDP_Test(WifiAwareRvrTest):
+    #Test cases
+    def test_aware_rvr_UDP_DL_ib_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=[False, False],
+        )
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_2'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_DL_ib_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_2'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=[False, False],
+        )
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', False])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '5G_1'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['2G_1', '2G_2'])
+        self._test_aware_rvr(testcase_params)
+
+    def test_aware_rvr_UDP_UL_ib_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            power_mode='INTERACTIVE',
+            dut_connected=['5G_1', '5G_2'])
+        self._test_aware_rvr(testcase_params)
diff --git a/acts_tests/tests/google/wifi/aware/performance/performance b/acts_tests/tests/google/wifi/aware/performance/performance
new file mode 100644
index 0000000..17e3963
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/performance/performance
@@ -0,0 +1,2 @@
+LatencyTest
+ThroughputTest
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py b/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
new file mode 100644
index 0000000..d2e95df
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DataPathStressTest(AwareBaseTest):
+
+    # Number of iterations on create/destroy Attach sessions.
+    ATTACH_ITERATIONS = 2
+
+    # Number of iterations on create/destroy NDP in each discovery session.
+    NDP_ITERATIONS = 50
+
+    # Maximum percentage of NDP setup failures over all iterations
+    MAX_FAILURE_PERCENTAGE = 1
+
+    ################################################################
+
+    def run_oob_ndp_stress(self,
+                           attach_iterations,
+                           ndp_iterations,
+                           trigger_failure_on_index=None):
+        """Run NDP (NAN data-path) stress test creating and destroying Aware
+    attach sessions, discovery sessions, and NDPs.
+
+    Args:
+      attach_iterations: Number of attach sessions.
+      ndp_iterations: Number of NDP to be attempted per attach session.
+      trigger_failure_on_index: Trigger a failure on this NDP iteration (the
+                                mechanism is to request NDP on Initiator
+                                before issuing the requeest on the Responder).
+                                If None then no artificial failure triggered.
+    """
+        init_dut = self.android_devices[0]
+        init_dut.pretty_name = 'Initiator'
+        resp_dut = self.android_devices[1]
+        resp_dut.pretty_name = 'Responder'
+
+        ndp_init_setup_success = 0
+        ndp_init_setup_failures = 0
+        ndp_resp_setup_success = 0
+        ndp_resp_setup_failures = 0
+        ndp_full_socket_success = 0
+
+        for attach_iter in range(attach_iterations):
+            init_id = init_dut.droid.wifiAwareAttach(True)
+            autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+            init_ident_event = autils.wait_for_event(
+                init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+            init_mac = init_ident_event['data']['mac']
+            time.sleep(self.device_startup_offset)
+            resp_id = resp_dut.droid.wifiAwareAttach(True)
+            autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+            resp_ident_event = autils.wait_for_event(
+                resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+            resp_mac = resp_ident_event['data']['mac']
+
+            # wait for for devices to synchronize with each other - there are no other
+            # mechanisms to make sure this happens for OOB discovery (except retrying
+            # to execute the data-path request)
+            time.sleep(autils.WAIT_FOR_CLUSTER)
+
+            for ndp_iteration in range(ndp_iterations):
+                if trigger_failure_on_index != ndp_iteration:
+                    # Responder: request network
+                    resp_req_key = autils.request_network(
+                        resp_dut,
+                        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac,
+                            None))
+
+                    # Wait a minimal amount of time to let the Responder configure itself
+                    # and be ready for the request. While calling it first may be
+                    # sufficient there are no guarantees that a glitch may slow the
+                    # Responder slightly enough to invert the setup order.
+                    time.sleep(1)
+
+                    # Initiator: request network
+                    init_req_key = autils.request_network(
+                        init_dut,
+                        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac,
+                            None))
+                else:
+                    # Initiator: request network
+                    init_req_key = autils.request_network(
+                        init_dut,
+                        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac,
+                            None))
+
+                    # Wait a minimal amount of time to let the Initiator configure itself
+                    # to guarantee failure!
+                    time.sleep(2)
+
+                    # Responder: request network
+                    resp_req_key = autils.request_network(
+                        resp_dut,
+                        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac,
+                            None))
+
+                init_ipv6 = None
+                resp_ipv6 = None
+
+                # Initiator: wait for network formation
+                got_on_available = False
+                got_on_link_props = False
+                got_on_net_cap = False
+                while not got_on_available or not got_on_link_props or not got_on_net_cap:
+                    try:
+                        nc_event = init_dut.ed.pop_event(
+                            cconsts.EVENT_NETWORK_CALLBACK,
+                            autils.EVENT_NDP_TIMEOUT)
+                        if nc_event['data'][
+                                cconsts.
+                                NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+                            got_on_available = True
+                        elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+                              cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+                            got_on_link_props = True
+                        elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+                              cconsts.NETWORK_CB_CAPABILITIES_CHANGED):
+                            got_on_net_cap = True
+                            if aconsts.NET_CAP_IPV6 in nc_event["data"]:
+                                resp_ipv6 = nc_event["data"][
+                                    aconsts.NET_CAP_IPV6]
+                    except queue.Empty:
+                        ndp_init_setup_failures = ndp_init_setup_failures + 1
+                        init_dut.log.info(
+                            '[Initiator] Timed out while waiting for '
+                            'EVENT_NETWORK_CALLBACK')
+                        break
+
+                if got_on_available and got_on_link_props and got_on_net_cap:
+                    ndp_init_setup_success = ndp_init_setup_success + 1
+
+                # Responder: wait for network formation
+                got_on_available = False
+                got_on_link_props = False
+                got_on_net_cap = False
+                while not got_on_available or not got_on_link_props or not got_on_net_cap:
+                    try:
+                        nc_event = resp_dut.ed.pop_event(
+                            cconsts.EVENT_NETWORK_CALLBACK,
+                            autils.EVENT_NDP_TIMEOUT)
+                        if nc_event['data'][
+                                cconsts.
+                                NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+                            got_on_available = True
+                        elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+                              cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+                            got_on_link_props = True
+                        elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+                              cconsts.NETWORK_CB_CAPABILITIES_CHANGED):
+                            got_on_net_cap = True
+                            if aconsts.NET_CAP_IPV6 in nc_event["data"]:
+                                init_ipv6 = nc_event["data"][
+                                    aconsts.NET_CAP_IPV6]
+                    except queue.Empty:
+                        ndp_resp_setup_failures = ndp_resp_setup_failures + 1
+                        init_dut.log.info(
+                            '[Responder] Timed out while waiting for '
+                            'EVENT_NETWORK_CALLBACK')
+                        break
+
+                if got_on_available and got_on_link_props and got_on_net_cap:
+                    ndp_resp_setup_success = ndp_resp_setup_success + 1
+
+                # open sockets to test connection
+                if autils.verify_socket_connect(init_dut, resp_dut, init_ipv6,
+                                                resp_ipv6, 0):
+                    if autils.verify_socket_connect(resp_dut, init_dut,
+                                                    resp_ipv6, init_ipv6, 0):
+                        ndp_full_socket_success = ndp_full_socket_success + 1
+
+                # clean-up
+                init_dut.droid.connectivityUnregisterNetworkCallback(
+                    init_req_key)
+                resp_dut.droid.connectivityUnregisterNetworkCallback(
+                    resp_req_key)
+
+            # clean-up at end of iteration
+            init_dut.droid.wifiAwareDestroy(init_id)
+            resp_dut.droid.wifiAwareDestroy(resp_id)
+
+        results = {}
+        results['ndp_init_setup_success'] = ndp_init_setup_success
+        results['ndp_init_setup_failures'] = ndp_init_setup_failures
+        results['ndp_resp_setup_success'] = ndp_resp_setup_success
+        results['ndp_resp_setup_failures'] = ndp_resp_setup_failures
+        results['ndp_full_socket_success'] = ndp_full_socket_success
+        max_failures = (self.MAX_FAILURE_PERCENTAGE * attach_iterations *
+                        ndp_iterations / 100)
+        if max_failures == 0:
+            max_failures = 1
+        if trigger_failure_on_index is not None:
+            max_failures = max_failures + 1  # for the triggered failure
+        asserts.assert_true(
+            (ndp_init_setup_failures + ndp_resp_setup_failures) <
+            (2 * max_failures),
+            'NDP setup failure rate exceeds threshold',
+            extras=results)
+        asserts.explicit_pass("test_oob_ndp_stress* done", extras=results)
+
+    @test_tracker_info(uuid="a20a96ba-e71f-4d31-b850-b88a75381981")
+    def test_oob_ndp_stress(self):
+        """Run NDP (NAN data-path) stress test creating and destroying Aware
+    attach sessions, discovery sessions, and NDPs."""
+        self.run_oob_ndp_stress(self.ATTACH_ITERATIONS, self.NDP_ITERATIONS)
+
+    @test_tracker_info(uuid="1fb4a383-bf1a-411a-a904-489dd9e29c6a")
+    def test_oob_ndp_stress_failure_case(self):
+        """Run NDP (NAN data-path) stress test creating and destroying Aware
+    attach sessions, discovery sessions, and NDPs.
+
+    Verify recovery from failure by triggering an artifical failure and
+    verifying that all subsequent iterations succeed.
+    """
+        self.run_oob_ndp_stress(
+            attach_iterations=1, ndp_iterations=10, trigger_failure_on_index=3)
diff --git a/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py b/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py
new file mode 100644
index 0000000..55545ea
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py
@@ -0,0 +1,113 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DiscoveryStressTest(AwareBaseTest):
+    """Stress tests for Discovery sessions"""
+
+    # Number of iterations on create/destroy Attach sessions.
+    ATTACH_ITERATIONS = 2
+
+    # Number of iterations on create/destroy Discovery sessions
+    DISCOVERY_ITERATIONS = 40
+
+    ####################################################################
+
+    @test_tracker_info(uuid="783791e5-7726-44e0-ac5b-98c1dbf493cb")
+    def test_discovery_stress(self):
+        """Create and destroy a random array of discovery sessions, up to the
+    limit of capabilities."""
+        dut = self.android_devices[0]
+
+        discovery_setup_success = 0
+        discovery_setup_fail = 0
+
+        for attach_iter in range(self.ATTACH_ITERATIONS):
+            # attach
+            session_id = dut.droid.wifiAwareAttach(True)
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+            p_discovery_ids = []
+            s_discovery_ids = []
+            for discovery_iter in range(self.DISCOVERY_ITERATIONS):
+                service_name = 'GoogleTestService-%d-%d' % (attach_iter,
+                                                            discovery_iter)
+
+                p_config = None
+                s_config = None
+
+                if discovery_iter % 4 == 0:  # publish/unsolicited
+                    p_config = autils.create_discovery_config(
+                        service_name, aconsts.PUBLISH_TYPE_UNSOLICITED)
+                elif discovery_iter % 4 == 1:  # publish/solicited
+                    p_config = autils.create_discovery_config(
+                        service_name, aconsts.PUBLISH_TYPE_SOLICITED)
+                elif discovery_iter % 4 == 2:  # subscribe/passive
+                    s_config = autils.create_discovery_config(
+                        service_name, aconsts.SUBSCRIBE_TYPE_PASSIVE)
+                elif discovery_iter % 4 == 3:  # subscribe/active
+                    s_config = autils.create_discovery_config(
+                        service_name, aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+                if p_config is not None:
+                    if len(p_discovery_ids) == dut.aware_capabilities[
+                            aconsts.CAP_MAX_PUBLISHES]:
+                        dut.droid.wifiAwareDestroyDiscoverySession(
+                            p_discovery_ids.pop(
+                                dut.aware_capabilities[aconsts.
+                                                       CAP_MAX_PUBLISHES] //
+                                2))
+                    disc_id = dut.droid.wifiAwarePublish(session_id, p_config)
+                    event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+                    p_discovery_ids.append(disc_id)
+                else:
+                    if len(s_discovery_ids) == dut.aware_capabilities[
+                            aconsts.CAP_MAX_SUBSCRIBES]:
+                        dut.droid.wifiAwareDestroyDiscoverySession(
+                            s_discovery_ids.pop(
+                                dut.aware_capabilities[aconsts.
+                                                       CAP_MAX_SUBSCRIBES] //
+                                2))
+                    disc_id = dut.droid.wifiAwareSubscribe(
+                        session_id, s_config)
+                    event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+                    s_discovery_ids.append(disc_id)
+
+                try:
+                    dut.ed.pop_event(event_name, autils.EVENT_TIMEOUT)
+                    discovery_setup_success = discovery_setup_success + 1
+                except queue.Empty:
+                    discovery_setup_fail = discovery_setup_fail + 1
+
+            dut.droid.wifiAwareDestroy(session_id)
+
+        results = {}
+        results['discovery_setup_success'] = discovery_setup_success
+        results['discovery_setup_fail'] = discovery_setup_fail
+        asserts.assert_equal(
+            discovery_setup_fail,
+            0,
+            'Discovery setup failures',
+            extras=results)
+        asserts.explicit_pass('test_discovery_stress done', extras=results)
diff --git a/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py b/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py
new file mode 100644
index 0000000..58e2c84
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py
@@ -0,0 +1,160 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import threading
+
+from acts import asserts
+from acts.test_utils.wifi import wifi_constants as wconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class InfraAssociationStressTest(AwareBaseTest):
+    # Length of test in seconds
+    TEST_DURATION_SECONDS = 300
+
+    # Service name
+    SERVICE_NAME = "GoogleTestServiceXYXYXY"
+
+    def is_associated(self, dut):
+        """Checks whether the device is associated (to any AP).
+
+    Args:
+      dut: Device under test.
+
+    Returns: True if associated (to any AP), False otherwise.
+    """
+        info = dut.droid.wifiGetConnectionInfo()
+        return info is not None and info["supplicant_state"] != "disconnected"
+
+    def wait_for_disassociation(self, q, dut):
+        """Waits for a disassociation event on the specified DUT for the given
+    timeout. Place a result into the queue (False) only if disassociation
+    observed.
+
+    Args:
+      q: The synchronization queue into which to place the results.
+      dut: The device to track.
+    """
+        try:
+            dut.ed.pop_event(wconsts.WIFI_DISCONNECTED,
+                             self.TEST_DURATION_SECONDS)
+            q.put(True)
+        except queue.Empty:
+            pass
+
+    def run_infra_assoc_oob_ndp_stress(self, with_ndp_traffic):
+        """Validates that Wi-Fi Aware NDP does not interfere with infrastructure
+    (AP) association.
+
+    Test assumes (and verifies) that device is already associated to an AP.
+
+    Args:
+      with_ndp_traffic: True to run traffic over the NDP.
+    """
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        # check that associated and start tracking
+        init_dut.droid.wifiStartTrackingStateChange()
+        resp_dut.droid.wifiStartTrackingStateChange()
+        asserts.assert_true(
+            self.is_associated(init_dut), "DUT is not associated to an AP!")
+        asserts.assert_true(
+            self.is_associated(resp_dut), "DUT is not associated to an AP!")
+
+        # set up NDP
+        (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+         resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+        self.log.info("Interface names: I=%s, R=%s", init_aware_if,
+                      resp_aware_if)
+        self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                      resp_ipv6)
+
+        # wait for any disassociation change events
+        q = queue.Queue()
+        init_thread = threading.Thread(
+            target=self.wait_for_disassociation, args=(q, init_dut))
+        resp_thread = threading.Thread(
+            target=self.wait_for_disassociation, args=(q, resp_dut))
+
+        init_thread.start()
+        resp_thread.start()
+
+        any_disassociations = False
+        try:
+            q.get(True, self.TEST_DURATION_SECONDS)
+            any_disassociations = True  # only happens on any disassociation
+        except queue.Empty:
+            pass
+        finally:
+            # TODO: no way to terminate thread (so even if we fast fail we still have
+            # to wait for the full timeout.
+            init_dut.droid.wifiStopTrackingStateChange()
+            resp_dut.droid.wifiStopTrackingStateChange()
+
+        asserts.assert_false(any_disassociations,
+                             "Wi-Fi disassociated during test run")
+
+    ################################################################
+
+    def test_infra_assoc_discovery_stress(self):
+        """Validates that Wi-Fi Aware discovery does not interfere with
+    infrastructure (AP) association.
+
+    Test assumes (and verifies) that device is already associated to an AP.
+    """
+        dut = self.android_devices[0]
+
+        # check that associated and start tracking
+        dut.droid.wifiStartTrackingStateChange()
+        asserts.assert_true(
+            self.is_associated(dut), "DUT is not associated to an AP!")
+
+        # attach
+        session_id = dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # publish
+        p_disc_id = dut.droid.wifiAwarePublish(
+            session_id,
+            autils.create_discovery_config(self.SERVICE_NAME,
+                                           aconsts.PUBLISH_TYPE_UNSOLICITED))
+        autils.wait_for_event(dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # wait for any disassociation change events
+        any_disassociations = False
+        try:
+            dut.ed.pop_event(wconsts.WIFI_DISCONNECTED,
+                             self.TEST_DURATION_SECONDS)
+            any_disassociations = True
+        except queue.Empty:
+            pass
+        finally:
+            dut.droid.wifiStopTrackingStateChange()
+
+        asserts.assert_false(any_disassociations,
+                             "Wi-Fi disassociated during test run")
+
+    def test_infra_assoc_ndp_no_traffic_stress(self):
+        """Validates that Wi-Fi Aware NDP (with no traffic) does not interfere with
+    infrastructure (AP) association.
+
+    Test assumes (and verifies) that devices are already associated to an AP.
+    """
+        self.run_infra_assoc_oob_ndp_stress(with_ndp_traffic=False)
diff --git a/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py b/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
new file mode 100644
index 0000000..2d2c514
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
@@ -0,0 +1,445 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+KEY_ID = "id"
+KEY_TX_OK_COUNT = "tx_ok_count"
+KEY_TX_FAIL_COUNT = "tx_fail_count"
+KEY_RX_COUNT = "rx_count"
+
+
+class MessagesStressTest(AwareBaseTest):
+    """Set of stress tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+    # Number of the message queue depth per Uid from framework
+    MESSAGE_QUEUE_DEPTH_PER_UID = 50
+
+    # Number of iterations in the stress test (number of messages)
+    # Should be larger than MESSAGE_QUEUE_DEPTH_PER_UID
+    NUM_ITERATIONS = 200
+
+    # Number of message to send per round to avoid exceed message queue depth limit
+    # Should be less than or equal to 1/2 of MESSAGE_QUEUE_DEPTH_PER_UID
+    NUM_PER_ROUND = 20
+    NUM_ROUNDS = 5
+
+    # Maximum permitted percentage of messages which fail to be transmitted
+    # correctly
+    MAX_TX_FAILURE_PERCENTAGE = 2
+
+    # Maximum permitted percentage of messages which are received more than once
+    # (indicating, most likely, that the ACK wasn't received and the message was
+    # retransmitted)
+    MAX_DUPLICATE_RX_PERCENTAGE = 2
+
+    SERVICE_NAME = "GoogleTestServiceXY"
+
+    def init_info(self, msg, id, messages_by_msg, messages_by_id):
+        """Initialize the message data structures.
+
+    Args:
+      msg: message text
+      id: message id
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+      messages_by_id: {id -> text}
+    """
+        messages_by_msg[msg] = {}
+        messages_by_msg[msg][KEY_ID] = id
+        messages_by_msg[msg][KEY_TX_OK_COUNT] = 0
+        messages_by_msg[msg][KEY_TX_FAIL_COUNT] = 0
+        messages_by_msg[msg][KEY_RX_COUNT] = 0
+        messages_by_id[id] = msg
+
+    def wait_for_tx_events(self, dut, num_msgs, messages_by_msg,
+                           messages_by_id):
+        """Wait for messages to be transmitted and update data structures.
+
+    Args:
+      dut: device under test
+      num_msgs: number of expected message tx
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+      messages_by_id: {id -> text}
+    """
+        num_ok_tx_confirmations = 0
+        num_fail_tx_confirmations = 0
+        num_unexpected_ids = 0
+        tx_events_regex = "%s|%s" % (aconsts.SESSION_CB_ON_MESSAGE_SEND_FAILED,
+                                     aconsts.SESSION_CB_ON_MESSAGE_SENT)
+        while num_ok_tx_confirmations + num_fail_tx_confirmations < num_msgs:
+            try:
+                events = dut.ed.pop_events(tx_events_regex,
+                                           autils.EVENT_TIMEOUT)
+                for event in events:
+                    if (event["name"] != aconsts.SESSION_CB_ON_MESSAGE_SENT
+                            and event["name"] !=
+                            aconsts.SESSION_CB_ON_MESSAGE_SEND_FAILED):
+                        asserts.fail("Unexpected event: %s" % event)
+                    is_tx_ok = event[
+                        "name"] == aconsts.SESSION_CB_ON_MESSAGE_SENT
+
+                    id = event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
+                    if id in messages_by_id:
+                        msg = messages_by_id[id]
+                        if is_tx_ok:
+                            messages_by_msg[msg][
+                                KEY_TX_OK_COUNT] = messages_by_msg[msg][KEY_TX_OK_COUNT] + 1
+                            if messages_by_msg[msg][KEY_TX_OK_COUNT] == 1:
+                                num_ok_tx_confirmations = num_ok_tx_confirmations + 1
+                        else:
+                            messages_by_msg[msg][KEY_TX_FAIL_COUNT] = (
+                                messages_by_msg[msg][KEY_TX_FAIL_COUNT] + 1)
+                            if messages_by_msg[msg][KEY_TX_FAIL_COUNT] == 1:
+                                num_fail_tx_confirmations = num_fail_tx_confirmations + 1
+                    else:
+                        self.log.warning(
+                            "Tx confirmation of unknown message ID received: %s",
+                            event)
+                        num_unexpected_ids = num_unexpected_ids + 1
+            except queue.Empty:
+                self.log.warning(
+                    "[%s] Timed out waiting for any MESSAGE_SEND* event - "
+                    "assuming the rest are not coming", dut.pretty_name)
+                break
+
+        return (num_ok_tx_confirmations, num_fail_tx_confirmations,
+                num_unexpected_ids)
+
+    def wait_for_rx_events(self, dut, num_msgs, messages_by_msg):
+        """Wait for messages to be received and update data structures
+
+    Args:
+      dut: device under test
+      num_msgs: number of expected messages to receive
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+    """
+        num_rx_msgs = 0
+        while num_rx_msgs < num_msgs:
+            try:
+                event = dut.ed.pop_event(
+                    aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                    autils.EVENT_TIMEOUT)
+                msg = event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+                if msg not in messages_by_msg:
+                    messages_by_msg[msg] = {}
+                    messages_by_msg[msg][KEY_ID] = -1
+                    messages_by_msg[msg][KEY_TX_OK_COUNT] = 0
+                    messages_by_msg[msg][KEY_TX_FAIL_COUNT] = 0
+                    messages_by_msg[msg][KEY_RX_COUNT] = 1
+
+                messages_by_msg[msg][
+                    KEY_RX_COUNT] = messages_by_msg[msg][KEY_RX_COUNT] + 1
+                if messages_by_msg[msg][KEY_RX_COUNT] == 1:
+                    num_rx_msgs = num_rx_msgs + 1
+            except queue.Empty:
+                self.log.warning(
+                    "[%s] Timed out waiting for ON_MESSAGE_RECEIVED event - "
+                    "assuming the rest are not coming", dut.pretty_name)
+                break
+
+    def analyze_results(self, results, messages_by_msg):
+        """Analyze the results of the stress message test and add to the results
+    dictionary
+
+    Args:
+      results: result dictionary into which to add data
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+    """
+        results["raw_data"] = messages_by_msg
+        results["tx_count_success"] = 0
+        results["tx_count_duplicate_success"] = 0
+        results["tx_count_fail"] = 0
+        results["tx_count_duplicate_fail"] = 0
+        results["tx_count_neither"] = 0
+        results["tx_count_tx_ok_but_no_rx"] = 0
+        results["rx_count"] = 0
+        results["rx_count_duplicate"] = 0
+        results["rx_count_no_ok_tx_indication"] = 0
+        results["rx_count_fail_tx_indication"] = 0
+        results["rx_count_no_tx_message"] = 0
+
+        for msg, data in messages_by_msg.items():
+            if data[KEY_TX_OK_COUNT] > 0:
+                results["tx_count_success"] = results["tx_count_success"] + 1
+            if data[KEY_TX_OK_COUNT] > 1:
+                results["tx_count_duplicate_success"] += 1
+            if data[KEY_TX_FAIL_COUNT] > 0:
+                results["tx_count_fail"] += 1
+            if data[KEY_TX_FAIL_COUNT] > 1:
+                results["tx_count_duplicate_fail"] += 1
+            if (data[KEY_TX_OK_COUNT] == 0 and data[KEY_TX_FAIL_COUNT] == 0
+                    and data[KEY_ID] != -1):
+                results["tx_count_neither"] += 1
+            if data[KEY_TX_OK_COUNT] > 0 and data[KEY_RX_COUNT] == 0:
+                results["tx_count_tx_ok_but_no_rx"] += 1
+            if data[KEY_RX_COUNT] > 0:
+                results["rx_count"] += 1
+            if data[KEY_RX_COUNT] > 1:
+                results["rx_count_duplicate"] += 1
+            if data[KEY_RX_COUNT] > 0 and data[KEY_TX_OK_COUNT] == 0:
+                results["rx_count_no_ok_tx_indication"] += 1
+            if data[KEY_RX_COUNT] > 0 and data[KEY_TX_FAIL_COUNT] > 0:
+                results["rx_count_fail_tx_indication"] += 1
+            if data[KEY_RX_COUNT] > 0 and data[KEY_ID] == -1:
+                results["rx_count_no_tx_message"] += 1
+
+    #######################################################################
+
+    @test_tracker_info(uuid="e88c060f-4ca7-41c1-935a-d3d62878ec0b")
+    def test_stress_message_no_throttling(self):
+        """Stress test for bi-directional message transmission and reception no throttling"""
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        # Start up a discovery session
+        discovery_data = autils.create_discovery_pair(
+            p_dut,
+            s_dut,
+            p_config=autils.create_discovery_config(
+                self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+            device_startup_offset=self.device_startup_offset,
+            msg_id=self.get_next_msg_id())
+        p_id = discovery_data[0]
+        s_id = discovery_data[1]
+        p_disc_id = discovery_data[2]
+        s_disc_id = discovery_data[3]
+        peer_id_on_sub = discovery_data[4]
+        peer_id_on_pub = discovery_data[5]
+
+        # Store information on Tx & Rx messages
+        messages_by_msg = {}  # keyed by message text
+        # {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+        messages_by_id = {}  # keyed by message ID {id -> text}
+        iterations = 0
+        p_tx_ok_count_total = 0
+        p_tx_fail_count_total = 0
+        p_tx_unknown_id_total = 0
+        s_tx_ok_count_total = 0
+        s_tx_fail_count_total = 0
+        s_tx_unknown_id_total = 0
+
+        # First round will fill up the message queue
+        num_of_messages_this_round = self.MESSAGE_QUEUE_DEPTH_PER_UID
+
+        # send messages (one in each direction) in rounds to avoid exceed the queue limit
+        for j in range(self.NUM_ROUNDS):
+            for k in range(num_of_messages_this_round):
+                msg_p2s = "Message Publisher -> Subscriber #%d" % iterations
+                next_msg_id = self.get_next_msg_id()
+                self.init_info(msg_p2s, next_msg_id, messages_by_msg,
+                               messages_by_id)
+                p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
+                                                 next_msg_id, msg_p2s, 0)
+
+                msg_s2p = "Message Subscriber -> Publisher #%d" % iterations
+                next_msg_id = self.get_next_msg_id()
+                self.init_info(msg_s2p, next_msg_id, messages_by_msg,
+                               messages_by_id)
+                s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                                 next_msg_id, msg_s2p, 0)
+                iterations += 1
+
+            # wait for message tx confirmation
+            (p_tx_ok_count, p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+                p_dut, self.NUM_PER_ROUND, messages_by_msg, messages_by_id)
+            p_tx_ok_count_total += p_tx_ok_count
+            p_tx_fail_count_total += p_tx_fail_count
+            p_tx_unknown_id_total += p_tx_unknown_id
+            (s_tx_ok_count, s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+                s_dut, self.NUM_PER_ROUND, messages_by_msg, messages_by_id)
+            s_tx_ok_count_total += s_tx_ok_count
+            s_tx_fail_count_total += s_tx_fail_count
+            s_tx_unknown_id_total += s_tx_unknown_id
+
+            num_of_messages_this_round = self.NUM_PER_ROUND
+
+        # wait for the rest message tx confirmation
+        p_tx_total = p_tx_ok_count_total + p_tx_fail_count_total + p_tx_unknown_id_total
+        s_tx_total = s_tx_ok_count_total + s_tx_fail_count_total + s_tx_unknown_id_total
+        (p_tx_ok_count, p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+            p_dut, iterations - p_tx_total, messages_by_msg, messages_by_id)
+        (s_tx_ok_count, s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+            s_dut, iterations - s_tx_total, messages_by_msg, messages_by_id)
+        p_tx_ok_count_total += p_tx_ok_count
+        p_tx_fail_count_total += p_tx_fail_count
+        p_tx_unknown_id_total += p_tx_unknown_id
+        s_tx_ok_count_total += s_tx_ok_count
+        s_tx_fail_count_total += s_tx_fail_count
+        s_tx_unknown_id_total += s_tx_unknown_id
+        self.log.info(
+            "Transmission done: pub=%d, sub=%d transmitted successfully",
+            p_tx_ok_count_total, s_tx_ok_count_total)
+
+        # wait for message rx confirmation (giving it the total number of messages
+        # transmitted rather than just those transmitted correctly since sometimes
+        # the Tx doesn't get that information correctly. I.e. a message the Tx
+        # thought was not transmitted correctly is actually received - missing ACK?
+        # bug?)
+        self.wait_for_rx_events(p_dut, iterations, messages_by_msg)
+        self.wait_for_rx_events(s_dut, iterations, messages_by_msg)
+
+        # analyze results
+        results = {}
+        results["tx_count"] = 2 * iterations
+        results["tx_unknown_ids"] = p_tx_unknown_id_total + s_tx_unknown_id_total
+        self.analyze_results(results, messages_by_msg)
+
+        # clear errors
+        asserts.assert_equal(results["tx_unknown_ids"], 0,
+                             "Message ID corruption", results)
+        asserts.assert_equal(results["tx_count_neither"], 0,
+                             "Tx message with no success or fail indication",
+                             results)
+        asserts.assert_equal(results["tx_count_duplicate_fail"], 0,
+                             "Duplicate Tx fail messages", results)
+        asserts.assert_equal(results["tx_count_duplicate_success"], 0,
+                             "Duplicate Tx success messages", results)
+        asserts.assert_equal(results["rx_count_no_tx_message"], 0,
+                             "Rx message which wasn't sent - message corruption?", results)
+        asserts.assert_equal(results["tx_count_tx_ok_but_no_rx"], 0,
+                             "Tx got ACK but Rx didn't get message", results)
+
+        # possibly ok - but flag since most frequently a bug
+        asserts.assert_equal(results["rx_count_no_ok_tx_indication"], 0,
+                             "Message received but Tx didn't get ACK", results)
+        asserts.assert_equal(results["rx_count_fail_tx_indication"], 0,
+                             "Message received but Tx didn't get ACK", results)
+
+        # permissible failures based on thresholds
+        asserts.assert_true(
+            results["tx_count_fail"] <=
+            (self.MAX_TX_FAILURE_PERCENTAGE * iterations * 2 / 100),
+            "Number of Tx failures exceeds threshold", extras=results)
+        asserts.assert_true(
+            results["rx_count_duplicate"] <=
+            (self.MAX_DUPLICATE_RX_PERCENTAGE * iterations * 2 / 100),
+            "Number of duplicate Rx exceeds threshold", extras=results)
+
+        asserts.explicit_pass("test_stress_message_no_throttling done", extras=results)
+
+    @test_tracker_info(uuid="546b0c6f-3071-4330-8e23-842ecbd07018")
+    def test_stress_message_throttling(self):
+        """Stress test for bi-directional message transmission and reception with throttling"""
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        # Start up a discovery session
+        discovery_data = autils.create_discovery_pair(
+            p_dut,
+            s_dut,
+            p_config=autils.create_discovery_config(
+                self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+            device_startup_offset=self.device_startup_offset,
+            msg_id=self.get_next_msg_id())
+        p_id = discovery_data[0]
+        s_id = discovery_data[1]
+        p_disc_id = discovery_data[2]
+        s_disc_id = discovery_data[3]
+        peer_id_on_sub = discovery_data[4]
+        peer_id_on_pub = discovery_data[5]
+
+        # Store information on Tx & Rx messages
+        messages_by_msg = {}  # keyed by message text
+        # {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+        messages_by_id = {}  # keyed by message ID {id -> text}
+
+        # send all messages at once (one in each direction)
+        for i in range(self.NUM_ITERATIONS):
+            msg_p2s = "Message Publisher -> Subscriber #%d" % i
+            next_msg_id = self.get_next_msg_id()
+            self.init_info(msg_p2s, next_msg_id, messages_by_msg,
+                           messages_by_id)
+            p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
+                                             next_msg_id, msg_p2s, 0)
+
+            msg_s2p = "Message Subscriber -> Publisher #%d" % i
+            next_msg_id = self.get_next_msg_id()
+            self.init_info(msg_s2p, next_msg_id, messages_by_msg,
+                           messages_by_id)
+            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                             next_msg_id, msg_s2p, 0)
+
+        # wait for message tx confirmation
+        (p_tx_ok_count,
+         p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+             p_dut, self.NUM_ITERATIONS, messages_by_msg, messages_by_id)
+        (s_tx_ok_count,
+         s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+             s_dut, self.NUM_ITERATIONS, messages_by_msg, messages_by_id)
+        self.log.info(
+            "Transmission done: pub=%d, sub=%d transmitted successfully",
+            p_tx_ok_count, s_tx_ok_count)
+
+        # wait for message rx confirmation (giving it the total number of messages
+        # transmitted rather than just those transmitted correctly since sometimes
+        # the Tx doesn't get that information correctly. I.e. a message the Tx
+        # thought was not transmitted correctly is actually received - missing ACK?
+        # bug?)
+        self.wait_for_rx_events(p_dut, self.NUM_ITERATIONS, messages_by_msg)
+        self.wait_for_rx_events(s_dut, self.NUM_ITERATIONS, messages_by_msg)
+
+        # analyze results
+        results = {}
+        results["tx_count"] = 2 * self.NUM_ITERATIONS
+        results["tx_unknown_ids"] = p_tx_unknown_id + s_tx_unknown_id
+        self.analyze_results(results, messages_by_msg)
+
+        # clear errors
+        asserts.assert_equal(results["tx_unknown_ids"], 0,
+                             "Message ID corruption", results)
+        asserts.assert_equal(results["tx_count_neither"], 0,
+                             "Tx message with no success or fail indication",
+                             results)
+        asserts.assert_equal(results["tx_count_duplicate_fail"], 0,
+                             "Duplicate Tx fail messages", results)
+        asserts.assert_equal(results["tx_count_duplicate_success"], 0,
+                             "Duplicate Tx success messages", results)
+        asserts.assert_equal(
+            results["rx_count_no_tx_message"], 0,
+            "Rx message which wasn't sent - message corruption?", results)
+        asserts.assert_equal(results["tx_count_tx_ok_but_no_rx"], 0,
+                             "Tx got ACK but Rx didn't get message", results)
+
+        # possibly ok - but flag since most frequently a bug
+        asserts.assert_equal(results["rx_count_no_ok_tx_indication"], 0,
+                             "Message received but Tx didn't get ACK", results)
+        asserts.assert_equal(results["rx_count_fail_tx_indication"], 0,
+                             "Message received but Tx didn't get ACK", results)
+
+        # permissible failures based on thresholds
+        asserts.assert_true(
+            results["rx_count_duplicate"] <=
+            (self.MAX_DUPLICATE_RX_PERCENTAGE * results["tx_count_success"] / 100),
+            "Number of duplicate Rx exceeds threshold", extras=results)
+
+        # check working status message queue limit per UID
+        asserts.assert_true(
+            results["tx_count_success"] >= self.MESSAGE_QUEUE_DEPTH_PER_UID * 2,
+            "Number of messages did not reach uid message queue limit", extras=results)
+        asserts.assert_true(
+            results["tx_count_success"] < self.NUM_ITERATIONS * 2,
+            "Seems uid message queue limit is not working, Tx all message", extras=results)
+
+        asserts.explicit_pass("test_stress_message_throttling done", extras=results)
diff --git a/acts_tests/tests/google/wifi/aware/stress/stress b/acts_tests/tests/google/wifi/aware/stress/stress
new file mode 100644
index 0000000..f79b158
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/stress/stress
@@ -0,0 +1,4 @@
+MessagesStressTest
+DataPathStressTest
+DiscoveryStressTest
+InfraAssociationStressTest
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/example_config_iot.json b/acts_tests/tests/google/wifi/example_config_iot.json
new file mode 100644
index 0000000..133cbe3
--- /dev/null
+++ b/acts_tests/tests/google/wifi/example_config_iot.json
@@ -0,0 +1,38 @@
+{
+    "_description": "This and example IOT WiFi testbed.",
+    "testbed": [
+        {
+            "_description": "WiFi testbed with 1 devices",
+            "name": "<test station name>",
+            "AndroidDevice": [
+                "<device serial>"
+            ],
+            "IPerfServer": [
+                5005
+            ]
+        }
+    ],
+    "logpath": "/tmp/ACTS_logs",
+    "testpaths": [
+        "<path to acts root>/tools/test/connectivity/acts_tests/tests/google/wifi"
+    ],
+    "iot_networks": [
+        {
+            "SSID": "<your SSID 2G>",
+            "password": "<your password>"
+        },
+        {
+            "SSID": "<your SSID 5G>",
+            "password": "<your password>"
+        },
+        {
+            "SSID": "<your SSID 2G 2>",
+            "password": "<your password>"
+        },
+        {
+            "SSID": "<your SSID 5G 2>",
+            "password": "<your password>"
+        }
+    ],
+    "iperf_server_address": "<your IP address>"
+}
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/example_config_sanity.json b/acts_tests/tests/google/wifi/example_config_sanity.json
new file mode 100644
index 0000000..b23d3bf
--- /dev/null
+++ b/acts_tests/tests/google/wifi/example_config_sanity.json
@@ -0,0 +1,207 @@
+{
+    "testbed": [
+        {
+            "name": "test_station_name",
+            "AndroidDevice": [
+                "<serial number 1>",
+                "<serial number 2 if necessary and 3 etc>"
+            ],
+            "AccessPoint": [
+                    { "ssh_config" :
+                                    {
+                                            "user" : "root",
+                                            "host" : "<ip 1, e.g. 192.168.1.2>"
+                                    }
+                    },
+                    { "ssh_config" :
+                                    {
+                                            "user" : "root",
+                                            "host" : "<ip 2 (if necessary) and ip 3 ...>"
+                                    }
+                    }
+            ],
+            "Attenuator": [
+                {
+                    "Address": "<attenuator ip address>",
+                    "InstrumentCount": 4,
+                    "Model": "<model, e.g. minicircuits>",
+                    "Paths": [
+                        "AP1-2G",
+                        "AP1-5G",
+                        "AP2-2G",
+                        "AP2-5G"
+                    ],
+                    "Port": 22
+                }
+            ],
+            "IPerfServer": [
+                5004
+            ],
+            "bssid_2g": {
+                "BSSID": "<bssid, e.g. 00:01:02:03:04:05>",
+                "high": "-10",
+                "low": "-85"
+            },
+            "bssid_5g": {
+                "BSSID": "<bssid>",
+                "high": "-10",
+                "low": "-85"
+            },
+            "bssid_dfs": {
+                "BSSID": "<bssid>",
+                "high": "-10",
+                "low": "-85"
+            },
+            "iperf_server_address": "100.107.126.31"
+        }
+    ],
+    "atten_val": {
+        "Ap1_2g": [
+            10,
+            95,
+            95,
+            95
+        ],
+        "Ap1_2gto5g": [
+            45,
+            10,
+            95,
+            95
+        ],
+        "Ap1_5gto2g": [
+            10,
+            80,
+            95,
+            95
+        ],
+        "Ap2_2g": [
+            75,
+            75,
+            10,
+            75
+        ],
+        "Ap2_2gto5g": [
+            75,
+            75,
+            75,
+            10
+        ],
+        "Ap2_5gto2g": [
+            75,
+            75,
+            10,
+            75
+        ],
+        "Back_from_blacklist": [
+            40,
+            95,
+            95
+        ],
+        "In_AP1_5gto2g": [
+            10,
+            75,
+            95,
+            95
+        ],
+        "In_Ap2_5gto2g": [
+            75,
+            75,
+            10,
+            75
+        ],
+        "In_blacklist": [
+            95,
+            95,
+            0
+        ],
+        "Swtich_AP1toAp2": [
+            70,
+            70,
+            2,
+            70
+        ],
+        "Swtich_AP2toAp1": [
+            10,
+            70,
+            75,
+            75
+        ],
+        "Swtich_to_blacklist": [
+            60,
+            90,
+            40
+        ]
+    },
+    "attenuator_id": 0,
+    "roaming_attn": {
+        "AP1_on_AP2_off": [
+            0,
+            0,
+            95,
+            95
+        ],
+        "AP1_off_AP2_on": [
+            95,
+            95,
+            0,
+            0
+        ],
+        "default": [
+            0,
+            0,
+            0,
+            0
+        ]
+    },
+    "attn_vals": {
+        "a_b_on": [
+            0,
+            0
+        ],
+        "a_on_b_off": [
+            0,
+            95
+        ],
+        "b_on_a_off": [
+            95,
+            0
+        ],
+        "default": [
+            0,
+            0
+        ]
+    },
+    "device_password": "hahahaha",
+    "eap_password": "password",
+    "fqdn": "red.com",
+    "max_bugreports": 5,
+    "other_network": {
+        "SSID": "wh_ap3_2g",
+        "password": "hahahaha"
+    },
+    "ping_addr": "https://www.google.com/robots.txt",
+    "pno_interval": 120,
+    "provider_friendly_name": "red",
+    "realm": "red.com",
+    "roam_interval": 60,
+    "run_extended_test": false,
+    "two_ap_testbed": true,
+    "aware_default_power_mode": "INTERACTIVE",
+    "stress_count": 100,
+    "stress_hours": 5,
+    "dbs_supported_models": ["<product name 1>", "<product name 2>"],
+    "lci_reference": [],
+    "lcr_reference": [],
+    "rtt_reference_distance_mm": 4600,
+    "stress_test_min_iteration_count": 100,
+    "stress_test_target_run_time_sec" : 30,
+    "energy_info_models": [
+        "<product name 1 (adb shell getprop ro.build.product)>",
+        "<product name 2>"
+    ],
+    "tdls_models": [
+        "<product name 1>",
+        "<product name 2>"
+    ]
+}
+
diff --git a/acts_tests/tests/google/wifi/example_connectivity_performance_ap_sta.json b/acts_tests/tests/google/wifi/example_connectivity_performance_ap_sta.json
new file mode 100644
index 0000000..6ec0501
--- /dev/null
+++ b/acts_tests/tests/google/wifi/example_connectivity_performance_ap_sta.json
@@ -0,0 +1,89 @@
+{
+ "testbed": [{
+        "name": "<your testbed name>",
+        "AndroidDevice": ["<your device serial number>"],
+        "bug_report": 1,
+	"RetailAccessPoints": ["<your ap configuration. see class definition in wifi_retail_ap.py>"],
+        "Attenuator": ["<your attenuator configuration. see attenuator class definition>"],
+        "main_network": {
+            "<your network name>": {
+                "SSID": "<your SSID>",
+                "password": "<your key>",
+                "BSSID": "<your BSSID>"
+            },
+            "<your other network names>": {
+                "SSID": "<your SSID>",
+                "password": "<your key>",
+                "BSSID": "<your BSSID>"
+            }
+        },
+        "IPerfServer": ["<your iperf server configuation. see class definition in iperf_server>"],
+	"testbed_params": {
+			 "default_region": "<default access point region to run tests in. This will be used for all non DFS channels>",
+			 "DFS_region": "<access point region to run DFS tests in>",
+                         "iperf_server_address": "<ip address of iperf server generating or accepting test traffic>",
+                         "fixed_attenuation": {"<your channel number 1>": "<your testbed attenuation on this channel>", "<your channel number 2>": "<your testbed attenuation on this channel>"},
+                 	 "dut_front_end_loss": {"<your channel number 1>": "<your DUT front end loss on this channel>", "<your channel number 2>": "<your DUT front end loss on this channel>"},
+			 "ap_tx_power": {"<your channel number 1>": "<your access point transmit power on this channel>", "<your channel number 2>": "<your access point transmit power on this channel>"},
+			 "golden_results_path": "<your full path to golden results used for pass fail check>"
+	}
+    }
+    ],
+    "rvr_test_params":{
+                         "country_code": "<device country code to set during rvr tests>",
+			 "iperf_duration": 30,
+			 "iperf_ignored_interval": 2,
+			 "UDP_rates": {"VHT20": "<throughput to transmit in this mode>", "VHT40": "<throughput to transmit in this mode>", "VHT80": "<throughput to transmit in this mode>"},
+                         "rvr_atten_start": 20,
+                         "rvr_atten_stop": 30,
+                         "rvr_atten_step": 5,
+			 "pct_tolerance": 5,
+			 "abs_tolerance": 5,
+			 "failure_count_tolerance": 1
+    },
+    "rssi_test_params":{
+			 "country_code": "<device country code to set during rvr tests>",
+                         "rssi_vs_atten_start": 20,
+                         "rssi_vs_atten_stop": 80,
+                         "rssi_vs_atten_step": 1,
+			 "rssi_vs_atten_connected_measurements": 10,
+			 "rssi_vs_atten_scan_measurements": 0,
+			 "rssi_vs_atten_metrics": ["signal_poll_rssi", "scan_rssi", "chain_0_rssi", "chain_1_rssi"],
+			 "rssi_stability_atten": [20, 55],
+			 "rssi_stability_duration": 10,
+			 "rssi_tracking_waveforms": [{"atten_levels": [40, 61, 40], "step_size": 1, "step_duration": 1, "repetitions":1}],
+			 "polling_frequency": 0.25,
+			 "abs_tolerance": 2.5,
+			 "stdev_tolerance": 1
+    },
+    "throughput_stability_test_params":{
+			 "country_code": "<device country code to set during rvr tests>",
+                         "iperf_duration": 30,
+			 "iperf_ignored_interval": 5,
+			 "UDP_rates": {"VHT20": "200M", "VHT40": "400M", "VHT80": "700M"},
+			 "low_rssi_backoff_from_range": 10,
+			 "min_throughput_threshold": 75,
+			 "std_deviation_threshold": 5
+
+    },
+    "ping_test_params":{
+			 "country_code": "<device country code to set during rvr tests>",
+			 "ping_size": 64,
+			 "range_ping_duration": 1,
+			 "range_ping_interval": 0.002,
+			 "range_atten_start": 60,
+			 "range_atten_step": 1,
+			 "range_atten_stop": 70,
+			 "range_ping_loss_threshold": 25,
+			 "range_gap_threshold": 2,
+			 "rtt_ping_duration": 30,
+			 "rtt_ping_interval": {"fast": 0.002, "slow": 0.5},
+			 "rtt_ignored_interval": 0.15,
+			 "rtt_test_attenuation": [20, 50],
+			 "rtt_test_percentile": 5,
+			 "rtt_threshold": 0.2,
+			 "rtt_std_deviation_threshold": 5
+    },
+    "logpath": "<path to logs>",
+    "testpaths": ["<path to ACTS root folder>/tools/test/connectivity/acts_tests/tests/google/wifi"]
+}
diff --git a/acts_tests/tests/google/wifi/p2p/config/wifi_p2p.json b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p.json
new file mode 100644
index 0000000..9a1a3ad
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p.json
@@ -0,0 +1,15 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi P2p tests.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi P2P testbed: auto-detect all attached devices",
+            "name": "WifiP2pAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "skip_read_factory_mac": 1,
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts_tests/tests/google/wifi/p2p"],
+    "adb_logcat_param": "-b all"
+}
diff --git a/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_group.json b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_group.json
new file mode 100644
index 0000000..2e03d52
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_group.json
@@ -0,0 +1,18 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi P2p group tests.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi P2P testbed: auto-detect all attached devices",
+            "name": "WifiP2pAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "skip_read_factory_mac": 1,
+    "network_name": "DIRECT-xy-Hello",
+    "passphrase": "P2pWorld1234",
+    "group_band": "2",
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts_tests/tests/google/wifi/p2p"],
+    "adb_logcat_param": "-b all"
+}
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py
new file mode 100644
index 0000000..dd27f21
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+import time
+
+from acts import asserts
+from acts import utils
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+
+WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
+WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
+WPS_KEYPAD = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD
+
+
+class WifiP2pGroupTest(WifiP2pBaseTest):
+    """Tests for APIs in Android's WifiP2pManager class.
+
+    Test Bed Requirement:
+    * At least two Android devices
+    """
+    def __init__(self, controllers):
+        WifiP2pBaseTest.__init__(self, controllers)
+
+    def setup_class(self):
+        super().setup_class()
+        if not "network_name" in self.user_params.keys():
+            self.log.error("Missing mandatory user config \"network_name\"!")
+        self.network_name = self.user_params["network_name"]
+        if not "passphrase" in self.user_params.keys():
+            self.log.error("Missing mandatory user config \"passphrase\"!")
+        self.passphrase = self.user_params["passphrase"]
+        if not "group_band" in self.user_params.keys():
+            self.log.error("Missing mandatory user config \"group_band\"!")
+        self.group_band = self.user_params["group_band"]
+
+    def setup_test(self):
+        super().setup_test()
+        self.dut1.droid.wifiP2pRemoveGroup()
+        self.dut2.droid.wifiP2pRemoveGroup()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    def teardown_test(self):
+        self.dut1.droid.wifiP2pRemoveGroup()
+        self.dut2.droid.wifiP2pRemoveGroup()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        super().teardown_test()
+
+    def p2p_group_join(self, wps_type):
+        """ General flow for p2p group join
+
+        Steps:
+        1. GO creates a group.
+        2. GC joins the group.
+        3. connection check via ping from GC to GO
+        """
+        go_dut = self.dut1
+        gc_dut = self.dut2
+        # Create a group
+        wp2putils.p2p_create_group(go_dut)
+        go_dut.ed.pop_event(p2pconsts.CONNECTED_EVENT,
+                            p2pconsts.DEFAULT_TIMEOUT)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        # Request the connection
+        wp2putils.p2p_connect(gc_dut,
+                              go_dut,
+                              False,
+                              wps_type,
+                              p2p_connect_type=p2pconsts.P2P_CONNECT_JOIN)
+
+        go_ip = wp2putils.p2p_go_ip(gc_dut)
+        wp2putils.p2p_connection_ping_test(gc_dut, go_ip)
+        # trigger disconnect
+        wp2putils.p2p_disconnect(go_dut)
+        wp2putils.check_disconnect(gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    """Test Cases"""
+    @test_tracker_info(uuid="c41f8293-5225-430d-917e-c294ddff7c2a")
+    def test_p2p_group_join_via_pbc(self):
+        """Verify the p2p creates a group and join this group via WPS PBC method.
+        """
+        self.p2p_group_join(WPS_PBC)
+
+    @test_tracker_info(uuid="56eb339f-d7e4-44f0-9802-6094e9255957")
+    def test_p2p_group_join_via_display(self):
+        """Verify the p2p creates a group and join this group via WPS DISPLAY method.
+        """
+        self.p2p_group_join(WPS_DISPLAY)
+
+    @test_tracker_info(uuid="27075cab-7859-49a7-afe9-b6cc6e8faddb")
+    def test_p2p_group_with_config(self):
+        """Verify the p2p creates a group and join an this group with config.
+
+        Steps:
+        1. GO creates a group with config.
+        2. GC joins the group with config.
+        3. connection check via ping from GC to GO
+        """
+        go_dut = self.dut1
+        gc_dut = self.dut2
+        # Create a group
+        wp2putils.p2p_create_group_with_config(go_dut, self.network_name,
+                                               self.passphrase,
+                                               self.group_band)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        # Request the connection. Since config is known, this is reconnection.
+        wp2putils.p2p_connect_with_config(gc_dut, go_dut, self.network_name,
+                                          self.passphrase, self.group_band)
+
+        go_ip = wp2putils.p2p_go_ip(gc_dut)
+        wp2putils.p2p_connection_ping_test(gc_dut, go_ip)
+        # trigger disconnect
+        wp2putils.p2p_disconnect(go_dut)
+        wp2putils.check_disconnect(gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py
new file mode 100644
index 0000000..b043eb9
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+
+
+class WifiP2pLocalServiceTest(WifiP2pBaseTest):
+    """Tests for APIs in Android's WifiP2pManager and p2p local service class.
+
+    Test Bed Requirement:
+    * At least two Android devices
+    * 3 Android devices for WifiP2pMultiPeersTest.py
+    """
+    def __init__(self, controllers):
+        WifiP2pBaseTest.__init__(self, controllers)
+
+    """Test Cases"""
+    @test_tracker_info(uuid="ba879c8d-0fbd-41fb-805c-5cd1cd312090")
+    def test_p2p_upnp_service(self):
+        """Verify the p2p discovery functionality
+        Steps:
+        1. dut1 add local Upnp service
+        2. dut2 register Upnp Service listener
+        3. Check dut2 peer list if it only included dut1
+        4. Setup p2p upnp local service request with different query string
+        5. Check p2p upnp local servier query result is expect or not
+        6. Test different query string and check query result
+        Note: Step 2 - Step 5 should reference function requestServiceAndCheckResult
+        """
+        self.log.info("Add local Upnp Service")
+        wp2putils.createP2pLocalService(self.dut1,
+                                        p2pconsts.P2P_LOCAL_SERVICE_UPNP)
+
+        wp2putils.requestServiceAndCheckResult(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_UPNP, None, None)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        wp2putils.requestServiceAndCheckResult(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_UPNP, "ssdp:all", None)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        wp2putils.requestServiceAndCheckResult(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_UPNP, "upnp:rootdevice", None)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    """Test Cases"""
+    @test_tracker_info(uuid="470306fa-5c46-4258-ade2-9c0834bb04f9")
+    def test_p2p_bonjour_service(self):
+        """Verify the p2p discovery functionality
+        Steps:
+        1. dut1 add local bonjour service - IPP and AFP
+        2. dut2 register bonjour Service listener - dnssd and dnssd_txrecord
+        3. Check dut2 peer list if it only included dut1
+        4. Setup p2p bonjour local service request with different query string
+        5. Check p2p bonjour local servier query result is expect or not
+        6. Test different query string and check query result
+        Note: Step 2 - Step 5 should reference function requestServiceAndCheckResult
+        """
+        self.log.info("Add local bonjour service to %s" % (self.dut1.name))
+        wp2putils.createP2pLocalService(self.dut1,
+                                        p2pconsts.P2P_LOCAL_SERVICE_IPP)
+        wp2putils.createP2pLocalService(self.dut1,
+                                        p2pconsts.P2P_LOCAL_SERVICE_AFP)
+
+        wp2putils.requestServiceAndCheckResultWithRetry(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_BONJOUR, None, None)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        wp2putils.requestServiceAndCheckResultWithRetry(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_BONJOUR, "_ipp._tcp", None)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        wp2putils.requestServiceAndCheckResultWithRetry(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_BONJOUR, "_ipp._tcp", "MyPrinter")
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        wp2putils.requestServiceAndCheckResultWithRetry(
+            self.dut1, self.dut2, wp2putils.WifiP2PEnums.WifiP2pServiceInfo.
+            WIFI_P2P_SERVICE_TYPE_BONJOUR, "_afpovertcp._tcp", "Example")
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py
new file mode 100644
index 0000000..6bda400
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+import time
+
+from acts import asserts
+from acts import utils
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+
+WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
+WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
+WPS_KEYPAD = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD
+
+
+class WifiP2pManagerTest(WifiP2pBaseTest):
+    """Tests for APIs in Android's WifiP2pManager class.
+
+    Test Bed Requirement:
+    * At least two Android devices
+    * 3 Android devices for WifiP2pMultiPeersTest.py
+    """
+    def __init__(self, controllers):
+        WifiP2pBaseTest.__init__(self, controllers)
+
+    """Test Cases"""
+    @test_tracker_info(uuid="28ddb16c-2ce4-44da-92f9-701d0dacc321")
+    def test_p2p_discovery(self):
+        """Verify the p2p discovery functionality
+        Steps:
+        1. Discover the target device
+        2. Check the target device in peer list
+        """
+        self.log.info("Device discovery")
+        wp2putils.find_p2p_device(self.dut1, self.dut2)
+        wp2putils.find_p2p_device(self.dut2, self.dut1)
+
+    @test_tracker_info(uuid="0016e6db-9b46-44fb-a53e-10a81eee955e")
+    def test_p2p_connect_via_pbc_and_ping_and_reconnect(self):
+        """Verify the p2p connect via pbc functionality
+
+        Steps:
+        1. Request the connection which include discover the target device
+        2. check which dut is GO and which dut is GC
+        3. connection check via ping from GC to GO
+        4. disconnect
+        5. Trigger connect again from GO for reconnect test.
+        6. GO trigger disconnect
+        7. Trigger connect again from GC for reconnect test.
+        8. GC trigger disconnect
+        """
+        # Request the connection
+        wp2putils.p2p_connect(self.dut1, self.dut2, False, WPS_PBC)
+
+        if wp2putils.is_go(self.dut1):
+            go_dut = self.dut1
+            gc_dut = self.dut2
+        elif wp2putils.is_go(self.dut2):
+            go_dut = self.dut2
+            gc_dut = self.dut1
+
+        go_ip = wp2putils.p2p_go_ip(gc_dut)
+        wp2putils.p2p_connection_ping_test(gc_dut, go_ip)
+
+        # trigger disconnect
+        wp2putils.p2p_disconnect(self.dut1)
+        wp2putils.check_disconnect(self.dut2)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        self.log.info("Reconnect test, triggered by GO")
+        # trigger reconnect from GO
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(go_dut, gc_dut, True, WPS_PBC)
+        wp2putils.p2p_disconnect(go_dut)
+        wp2putils.check_disconnect(gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # trigger reconnect from GC
+        self.log.info("Reconnect test, triggered by GC")
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(gc_dut, go_dut, True, WPS_PBC)
+        wp2putils.p2p_disconnect(gc_dut)
+        wp2putils.check_disconnect(
+            go_dut, timeout=p2pconsts.DEFAULT_GROUP_CLIENT_LOST_TIME)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    @test_tracker_info(uuid="12bbe73a-5a6c-4307-9797-c77c7efdc4b5")
+    def test_p2p_connect_via_display_and_ping_and_reconnect(self):
+        """Verify the p2p connect via display functionality
+
+        Steps:
+        1. Request the connection which include discover the target device
+        2. check which dut is GO and which dut is GC
+        3. connection check via ping from GC to GO
+        4. disconnect
+        5. Trigger connect again from GO for reconnect test.
+        6. GO trigger disconnect
+        7. Trigger connect again from GC for reconnect test.
+        8. GC trigger disconnect
+        """
+        # Request the connection
+        wp2putils.p2p_connect(self.dut1, self.dut2, False, WPS_DISPLAY)
+
+        if wp2putils.is_go(self.dut1):
+            go_dut = self.dut1
+            gc_dut = self.dut2
+        elif wp2putils.is_go(self.dut2):
+            go_dut = self.dut2
+            gc_dut = self.dut1
+
+        go_ip = wp2putils.p2p_go_ip(gc_dut)
+        wp2putils.p2p_connection_ping_test(gc_dut, go_ip)
+
+        # trigger disconnect
+        wp2putils.p2p_disconnect(self.dut1)
+        wp2putils.check_disconnect(self.dut2)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        self.log.info("Reconnect test, triggered by GO")
+        # trigger reconnect from GO
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(go_dut, gc_dut, True, WPS_DISPLAY)
+        wp2putils.p2p_disconnect(go_dut)
+        wp2putils.check_disconnect(gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # trigger reconnect from GC
+        self.log.info("Reconnect test, triggered by GC")
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(gc_dut, go_dut, True, WPS_DISPLAY)
+        wp2putils.p2p_disconnect(gc_dut)
+        wp2putils.check_disconnect(
+            go_dut, timeout=p2pconsts.DEFAULT_GROUP_CLIENT_LOST_TIME)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    @test_tracker_info(uuid="efe88f57-5a08-4195-9592-2f6945a9d18a")
+    def test_p2p_connect_via_keypad_and_ping_and_reconnect(self):
+        """Verify the p2p connect via keypad functionality
+
+        Steps:
+        1. Request the connection which include discover the target device
+        2. check which dut is GO and which dut is GC
+        3. connection check via ping from GC to GO
+        4. disconnect
+        5. Trigger connect again from GO for reconnect test.
+        6. GO trigger disconnect
+        7. Trigger connect again from GC for reconnect test.
+        8. GC trigger disconnect
+        """
+        # Request the connection
+        wp2putils.p2p_connect(self.dut1, self.dut2, False, WPS_KEYPAD)
+
+        if wp2putils.is_go(self.dut1):
+            go_dut = self.dut1
+            gc_dut = self.dut2
+        elif wp2putils.is_go(self.dut2):
+            go_dut = self.dut2
+            gc_dut = self.dut1
+
+        go_ip = wp2putils.p2p_go_ip(gc_dut)
+        wp2putils.p2p_connection_ping_test(gc_dut, go_ip)
+
+        # trigger disconnect
+        wp2putils.p2p_disconnect(self.dut1)
+        wp2putils.check_disconnect(self.dut2)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        self.log.info("Reconnect test, triggered by GO")
+        # trigger reconnect from GO
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(go_dut, gc_dut, True, WPS_KEYPAD)
+        wp2putils.p2p_disconnect(go_dut)
+        wp2putils.check_disconnect(gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # trigger reconnect from GC
+        self.log.info("Reconnect test, triggered by GC")
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(gc_dut, go_dut, True, WPS_KEYPAD)
+        wp2putils.p2p_disconnect(gc_dut)
+        wp2putils.check_disconnect(
+            go_dut, timeout=p2pconsts.DEFAULT_GROUP_CLIENT_LOST_TIME)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py
new file mode 100644
index 0000000..2951be6
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+import time
+
+from acts import asserts
+from acts import utils
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+
+WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
+WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
+WPS_KEYPAD = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiP2pMultiPeersTest(WifiP2pBaseTest):
+    """Tests for multiple clients.
+
+    Test Bed Requirement:
+    * 3 Android devices for each test in this class.
+    """
+    def __init__(self, controllers):
+        WifiP2pBaseTest.__init__(self, controllers)
+
+    def setup_test(self):
+        WifiP2pBaseTest.setup_test(self)
+        asserts.skip_if(
+            len(self.android_devices) < 3,
+            "No enough android devices. Skip this test")
+
+    def form_group(self, dut1, dut2, isReconnect=False, wpsType=WPS_PBC):
+        # Request the connection to create a group
+        wp2putils.p2p_connect(dut1, dut2, isReconnect, wpsType)
+
+        if wp2putils.is_go(dut1):
+            go_dut = dut1
+            gc_dut = dut2
+        elif wp2putils.is_go(dut2):
+            go_dut = dut2
+            gc_dut = dut1
+        return (go_dut, gc_dut)
+
+    def verify_group_connection(self, group_clients):
+        for gc in group_clients:
+            go_ip = wp2putils.p2p_go_ip(gc)
+            wp2putils.p2p_connection_ping_test(gc, go_ip)
+
+    def clear_all_events(self, duts):
+        for dut in duts:
+            dut.ed.clear_all_events()
+
+    def check_disconnection(self, duts):
+        for dut in duts:
+            wp2putils.check_disconnect(dut)
+
+    """Test Cases"""
+    @test_tracker_info(uuid="20cd4f4d-fe7d-4ee2-a832-33caa5b9700b")
+    def test_p2p_multi_clients_group_removal_behavior(self):
+        """Verify the p2p group removal behavior
+
+        Steps:
+        1. form a group between 3 peers
+        2. verify their connection
+        3. disconnect
+        4. form a group between 3 peers
+        5. trigger disconnect from GO
+        6. check the group is removed
+        7. form a group between 3 peers
+        8. trigger disconnect from a GC
+        9. check the group is still alive
+        10. disconnect
+        """
+        all_duts = [self.dut1, self.dut2, self.dut3]
+
+        # the 1st round
+        (go_dut, gc_dut) = self.form_group(self.dut1, self.dut2)
+        gc2_dut = self.dut3
+        wp2putils.p2p_connect(gc2_dut,
+                              go_dut,
+                              False,
+                              WPS_PBC,
+                              p2p_connect_type=p2pconsts.P2P_CONNECT_JOIN)
+
+        self.verify_group_connection([gc_dut, gc2_dut])
+
+        go_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(go_dut)
+        self.check_disconnection([gc_dut, gc2_dut])
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # the 2nd round
+        self.log.info("Reconnect test, triggered by GO")
+        self.clear_all_events(all_duts)
+
+        self.form_group(go_dut, gc_dut, isReconnect=True)
+        wp2putils.p2p_connect(gc2_dut,
+                              go_dut,
+                              True,
+                              WPS_PBC,
+                              p2p_connect_type=p2pconsts.P2P_CONNECT_JOIN)
+
+        # trigger disconnect from GO, the group is destroyed and all
+        # client are disconnected.
+        go_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(go_dut)
+        self.check_disconnection([gc_dut, gc2_dut])
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # the 3rd round
+        self.log.info("Reconnect test, triggered by GC")
+        self.clear_all_events(all_duts)
+
+        self.form_group(go_dut, gc_dut, isReconnect=True)
+        wp2putils.p2p_connect(gc2_dut,
+                              go_dut,
+                              True,
+                              WPS_PBC,
+                              p2p_connect_type=p2pconsts.P2P_CONNECT_JOIN)
+
+        # trigger disconnect from GC, the group is still there.
+        gc_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(gc_dut)
+        self.verify_group_connection([
+            gc2_dut,
+        ])
+
+        # all clients are disconnected, the group is removed.
+        wp2putils.p2p_disconnect(gc2_dut)
+        self.check_disconnection([
+            go_dut,
+        ])
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    @test_tracker_info(uuid="6ea6e802-df62-4ae2-aa15-44c3267fd99b")
+    def test_p2p_connect_with_p2p_and_join_go(self):
+        """Verify the invitation from GC
+
+        Steps:
+        1. form a group between 2 peers
+        2. gc joins the group via go
+        2. verify their connection
+        3. disconnect
+        """
+        all_duts = [self.dut1, self.dut2, self.dut3]
+
+        (go_dut, gc_dut) = self.form_group(self.dut1, self.dut2)
+        gc2_dut = self.dut3
+        wp2putils.p2p_connect(gc2_dut,
+                              go_dut,
+                              False,
+                              WPS_PBC,
+                              p2p_connect_type=p2pconsts.P2P_CONNECT_JOIN)
+
+        self.verify_group_connection([gc_dut, gc2_dut])
+
+        go_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(go_dut)
+        self.check_disconnection([gc_dut, gc2_dut])
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    @test_tracker_info(uuid="e00469a4-93b7-44dd-8a5e-5d317e0e9333")
+    def test_p2p_connect_with_p2p_and_legacy_client(self):
+        """Verify the invitation from GC
+
+        Steps:
+        1. form a group between 2 peers
+        2. gc joins the group via go
+        2. verify their connection
+        3. disconnect
+        """
+        all_duts = [self.dut1, self.dut2, self.dut3]
+
+        (go_dut, gc_dut) = self.form_group(self.dut1, self.dut2)
+        gc2_dut = self.dut3
+
+        group = wp2putils.p2p_get_current_group(go_dut)
+        network = {
+            WifiEnums.SSID_KEY: group['NetworkName'],
+            WifiEnums.PWD_KEY: group['Passphrase']
+        }
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            gc2_dut, group['NetworkName'])
+        wutils.wifi_connect(gc2_dut, network, num_of_tries=3)
+
+        go_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(go_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    @test_tracker_info(uuid="bd53cc18-bcc7-4e27-b78b-1506f5c098c5")
+    def test_p2p_connect_with_p2p_and_go_invite_peer(self):
+        """Verify the invitation from GC
+
+        Steps:
+        1. form a group between 2 peers
+        2. gc joins the group via go
+        2. verify their connection
+        3. disconnect
+        """
+        all_duts = [self.dut1, self.dut2, self.dut3]
+
+        (go_dut, gc_dut) = self.form_group(self.dut1, self.dut2)
+        gc2_dut = self.dut3
+        wp2putils.p2p_connect(
+            go_dut,
+            gc2_dut,
+            False,
+            WPS_PBC,
+            p2p_connect_type=p2pconsts.P2P_CONNECT_INVITATION,
+            go_ad=go_dut)
+
+        self.verify_group_connection([gc_dut, gc2_dut])
+
+        go_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(go_dut)
+        self.check_disconnection([gc_dut, gc2_dut])
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    @test_tracker_info(uuid="4d6e666d-dc48-4881-86c1-5d7cec5e2571")
+    def test_p2p_connect_with_p2p_and_gc_invite_peer(self):
+        """Verify the invitation from GC
+
+        Steps:
+        1. form a group between 2 peers
+        2. gc joins the group via go
+        2. verify their connection
+        3. disconnect
+        """
+        all_duts = [self.dut1, self.dut2, self.dut3]
+
+        (go_dut, gc_dut) = self.form_group(self.dut1, self.dut2)
+        gc2_dut = self.dut3
+        wp2putils.p2p_connect(
+            gc_dut,
+            gc2_dut,
+            False,
+            WPS_PBC,
+            p2p_connect_type=p2pconsts.P2P_CONNECT_INVITATION,
+            go_ad=go_dut)
+
+        self.verify_group_connection([gc_dut, gc2_dut])
+
+        go_dut.log.info("Trigger disconnection")
+        wp2putils.p2p_disconnect(go_dut)
+        self.check_disconnection([gc_dut, gc2_dut])
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
new file mode 100644
index 0000000..2db5273
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts.utils
+import time
+import re
+
+from acts import asserts
+from acts import utils
+
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts.controllers.ap_lib.hostapd_constants import BAND_2G
+from scapy.all import *
+
+WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
+WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
+WPS_KEYPAD = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD
+DEFAULT_TIMEOUT = 10
+
+
+class WifiP2pSnifferTest(WifiP2pBaseTest):
+    """Tests factory MAC is not leaked for p2p discovery and associated cases.
+
+    Test Bed Requirement:
+    * At least two Android devices
+    * An access point as sniffer
+    """
+    def __init__(self, controllers):
+        WifiP2pBaseTest.__init__(self, controllers)
+
+    def setup_class(self):
+        super(WifiP2pSnifferTest, self).setup_class()
+        wp2putils.wifi_p2p_set_channels_for_current_group(self.dut1, 6, 6)
+        wp2putils.wifi_p2p_set_channels_for_current_group(self.dut2, 6, 6)
+        self.configure_packet_capture()
+
+    def setup_test(self):
+        super(WifiP2pSnifferTest, self).setup_test()
+        self.pcap_procs = wutils.start_pcap(self.packet_capture, '2g',
+                                            self.test_name)
+
+    def teardown_test(self):
+        if self.pcap_procs:
+            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        super(WifiP2pSnifferTest, self).teardown_test()
+
+    def configure_packet_capture(self):
+        """Configure packet capture on the social channels."""
+        self.packet_capture = self.packet_capture[0]
+        result = self.packet_capture.configure_monitor_mode(BAND_2G, 6)
+        if not result:
+            raise ValueError("Failed to configure channel for 2G band")
+
+    def verify_mac_no_leakage(self):
+        time.sleep(DEFAULT_TIMEOUT)
+        self.log.info("Stopping packet capture")
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        # Verify factory MAC is not leaked in 2G pcaps
+        pcap_fname = '%s_%s.pcap' % (self.pcap_procs[BAND_2G][1],
+                                     BAND_2G.upper())
+        self.pcap_procs = None
+        packets = rdpcap(pcap_fname)
+        wutils.verify_mac_not_found_in_pcap(self.dut1, self.dut1_mac, packets)
+        wutils.verify_mac_not_found_in_pcap(self.dut2, self.dut2_mac, packets)
+
+    """Test Cases"""
+    @test_tracker_info(uuid="d04e62dc-e1ef-4cea-86e6-39f0dd08fb6b")
+    def test_p2p_discovery_sniffer(self):
+        """Verify the p2p discovery functionality
+        Steps:
+        1. Discover the target device
+        2. Check the target device in peer list
+        """
+        self.log.info("Device discovery")
+        wp2putils.find_p2p_device(self.dut1, self.dut2)
+        wp2putils.find_p2p_device(self.dut2, self.dut1)
+        self.verify_mac_no_leakage()
+
+    @test_tracker_info(uuid="6a02be84-912d-4b5b-8dfa-fd80d2554c55")
+    def test_p2p_connect_via_pbc_and_ping_and_reconnect_sniffer(self):
+        """Verify the p2p connect via pbc functionality
+
+        Steps:
+        1. Request the connection which include discover the target device
+        2. check which dut is GO and which dut is GC
+        3. connection check via ping from GC to GO
+        4. disconnect
+        5. Trigger connect again from GO for reconnect test.
+        6. GO trigger disconnect
+        7. Trigger connect again from GC for reconnect test.
+        8. GC trigger disconnect
+        """
+        # Request the connection
+        wp2putils.p2p_connect(self.dut1, self.dut2, False, WPS_PBC)
+
+        if wp2putils.is_go(self.dut1):
+            go_dut = self.dut1
+            gc_dut = self.dut2
+        elif wp2putils.is_go(self.dut2):
+            go_dut = self.dut2
+            gc_dut = self.dut1
+
+        go_ip = wp2putils.p2p_go_ip(gc_dut)
+        wp2putils.p2p_connection_ping_test(gc_dut, go_ip)
+
+        # trigger disconnect
+        wp2putils.p2p_disconnect(self.dut1)
+        wp2putils.check_disconnect(self.dut2)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        self.log.info("Reconnect test, triggered by GO")
+        # trigger reconnect from GO
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(go_dut, gc_dut, True, WPS_PBC)
+        wp2putils.p2p_disconnect(go_dut)
+        wp2putils.check_disconnect(gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # trigger reconnect from GC
+        self.log.info("Reconnect test, triggered by GC")
+        go_dut.ed.clear_all_events()
+        gc_dut.ed.clear_all_events()
+        wp2putils.p2p_connect(gc_dut, go_dut, True, WPS_PBC)
+        wp2putils.p2p_disconnect(gc_dut)
+        wp2putils.check_disconnect(go_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # teardown
+        self.verify_mac_no_leakage()
diff --git a/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
new file mode 100644
index 0000000..85b9a0d
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
@@ -0,0 +1,777 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import collections
+import logging
+import os
+import re
+import time
+from acts import asserts
+from acts import base_test
+from acts import utils
+from acts.controllers import iperf_server as ipf
+from acts.controllers import iperf_client as ipc
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.wifi import ota_sniffer
+from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from WifiRvrTest import WifiRvrTest
+
+AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
+                                          ['ap_settings'])
+
+
+class WifiP2pRvrTest(WifiRvrTest):
+    def __init__(self, controllers):
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.testcase_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_case())
+        self.testclass_metric_logger = (
+            BlackboxMappedMetricLogger.for_test_class())
+        self.publish_testcase_metrics = True
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardwares and compiles parameters that are
+        common to all tests in this class.
+        """
+        req_params = ['p2p_rvr_test_params', 'testbed_params']
+        opt_params = ['RetailAccessPoints', 'ap_networks', 'OTASniffer']
+        self.unpack_userparams(req_params, opt_params)
+        if hasattr(self, 'RetailAccessPoints'):
+            self.access_points = retail_ap.create(self.RetailAccessPoints)
+            self.access_point = self.access_points[0]
+        else:
+            self.access_point = AccessPointTuple({})
+        self.testclass_params = self.p2p_rvr_test_params
+        self.num_atten = self.attenuators[0].instrument.num_atten
+        self.iperf_server = ipf.create([{
+            'AndroidDevice':
+            self.android_devices[0].serial,
+            'port':
+            '5201'
+        }])[0]
+        self.iperf_client = ipc.create([{
+            'AndroidDevice':
+            self.android_devices[1].serial,
+            'port':
+            '5201'
+        }])[0]
+        if hasattr(self,
+                   'OTASniffer') and self.testbed_params['sniffer_enable']:
+            self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+        if not hasattr(self, 'golden_files_list'):
+            if 'golden_results_path' in self.testbed_params:
+                self.golden_files_list = [
+                    os.path.join(self.testbed_params['golden_results_path'],
+                                 file) for file in
+                    os.listdir(self.testbed_params['golden_results_path'])
+                ]
+            else:
+                self.log.warning('No golden files found.')
+                self.golden_files_list = []
+
+        self.testclass_results = []
+
+        # Turn WiFi ON
+        for ad in self.android_devices:
+            self.init_device(ad)
+
+        # Configure test retries
+        self.user_params['retry_tests'] = [self.__class__.__name__]
+
+    def init_device(self, ad):
+        asserts.assert_true(utils.force_airplane_mode(ad, False),
+                            "Can not turn off airplane mode.")
+        utils.set_location_service(ad, True)
+        ad.droid.wifiScannerToggleAlwaysAvailable(False)
+        asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(),
+                            "Failed to turn off location service's scan.")
+        wutils.reset_wifi(ad)
+        utils.sync_device_time(ad)
+        ad.droid.telephonyToggleDataConnection(False)
+        country_code = self.testclass_params.get('country_code', 'US')
+        wutils.set_wifi_country_code(ad, country_code)
+        ad.droid.wifiP2pInitialize()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        asserts.assert_true(
+            ad.droid.wifiP2pIsEnabled(),
+            "{} p2p was not properly initialized".format(ad.serial))
+        ad.name = "Android_" + ad.serial
+        ad.droid.wifiP2pSetDeviceName(ad.name)
+
+    def teardown_class(self):
+        # Turn WiFi OFF
+        for ad in self.android_devices:
+            ad.droid.wifiP2pClose()
+            utils.set_location_service(ad, False)
+            #wutils.wifi_toggle_state(ad, False)
+        self.process_testclass_results()
+        # Teardown AP and release its lockfile
+        self.access_point.teardown()
+
+    def setup_test(self):
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+            ad.ed.clear_all_events()
+            ad.droid.wifiP2pRemoveGroup()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    def teardown_test(self):
+        self.iperf_server.stop()
+        for ad in self.android_devices:
+            ad.droid.wifiP2pRemoveGroup()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        for ad in self.android_devices:
+            # Clear p2p group info
+            ad.droid.wifiP2pRequestPersistentGroupInfo()
+            event = ad.ed.pop_event("WifiP2pOnPersistentGroupInfoAvailable",
+                                    p2pconsts.DEFAULT_TIMEOUT)
+            for network in event['data']:
+                ad.droid.wifiP2pDeletePersistentGroup(network['NetworkId'])
+            # Clear p2p local service
+            ad.droid.wifiP2pClearLocalServices()
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+
+    def setup_aps(self, testcase_params):
+        for network in testcase_params['ap_networks']:
+            self.log.info('Setting AP {} {} interface on channel {}'.format(
+                network['ap_id'], network['interface_id'], network['channel']))
+            self.access_points[network['ap_id']].set_channel(
+                network['interface_id'], network['channel'])
+
+    def setup_duts(self, testcase_params):
+        # Check battery level before test
+        for ad in self.android_devices:
+            if not wputils.health_check(ad, 20):
+                asserts.skip('Overheating or Battery low. Skipping test.')
+            ad.go_to_sleep()
+            wutils.reset_wifi(ad)
+        # Turn BT on or off
+        bt_status = self.testclass_params.get('bluetooth_enabled', 1)
+        self.log.info('Setting Bluetooth status to {}.'.format(bt_status))
+        for ad in self.android_devices:
+            ad.droid.bluetoothToggleState(bt_status)
+        # Turn screen off to preserve battery
+        for network in testcase_params['ap_networks']:
+            for connected_dut in network['connected_dut']:
+                self.log.info("Connecting DUT {} to {}".format(
+                    connected_dut, self.ap_networks[network['ap_id']][
+                        network['interface_id']]))
+                wutils.wifi_connect(self.android_devices[connected_dut],
+                                    self.ap_networks[network['ap_id']][
+                                        network['interface_id']],
+                                    num_of_tries=5,
+                                    check_connectivity=True)
+
+    def get_p2p_mac_address(self, ad):
+        """Gets the current MAC address being used for Wi-Fi Direct."""
+        out = ad.adb.shell("ifconfig p2p0")
+        return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
+
+    def _setup_p2p_connection_join_group(self, testcase_params):
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.start_capture(network={'SSID': 'dummy'},
+                                       chan=11,
+                                       bw=20,
+                                       duration=180)
+        # Create a group
+        self.go_dut = self.android_devices[0]
+        self.gc_dut = self.android_devices[1]
+        wp2putils.p2p_create_group(self.go_dut)
+        self.go_dut.ed.pop_event(p2pconsts.CONNECTED_EVENT,
+                                 p2pconsts.DEFAULT_TIMEOUT)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        # Request the connection
+        try:
+            wp2putils.p2p_connect(
+                self.gc_dut,
+                self.go_dut,
+                isReconnect=False,
+                p2p_connect_type=p2pconsts.P2P_CONNECT_JOIN,
+                wpsSetup=wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC)
+        except Exception as e:
+            # Stop sniffer
+            if self.testbed_params['sniffer_enable']:
+                self.sniffer.stop_capture(tag='connection_setup')
+            raise e
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.stop_capture(tag='connection_setup')
+
+    def _setup_p2p_connection_negotiation(self, testcase_params):
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.start_capture(network={'SSID': 'dummy'},
+                                       chan=11,
+                                       bw=20,
+                                       duration=180)
+        try:
+            wp2putils.p2p_connect(
+                self.android_devices[0],
+                self.android_devices[1],
+                False,
+                wpsSetup=wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC)
+            if wp2putils.is_go(self.android_devices[0]):
+                self.go_dut = self.android_devices[0]
+                self.gc_dut = self.android_devices[1]
+            elif wp2putils.is_go(self.android_devices[1]):
+                self.go_dut = self.android_devices[1]
+                self.gc_dut = self.android_devices[0]
+        except Exception as e:
+            # Stop sniffer
+            if self.testbed_params['sniffer_enable']:
+                self.sniffer.stop_capture(tag='connection_setup')
+            raise e
+        if self.testbed_params['sniffer_enable']:
+            self.sniffer.stop_capture(tag='connection_setup')
+
+    def _get_gc_ip(self, subnet_mask='255.255.255.0'):
+        subnet_mask = ['255', '255', '255', '0']
+        go_ip = wp2putils.p2p_go_ip(self.gc_dut)
+        dut_subnet = [
+            int(dut) & int(subnet)
+            for dut, subnet in zip(go_ip.split('.'), subnet_mask)
+        ]
+        ifconfig_out = self.gc_dut.adb.shell('ifconfig')
+        ip_list = re.findall('inet (?:addr:)?(\d+.\d+.\d+.\d+)', ifconfig_out)
+        for current_ip in ip_list:
+            current_subnet = [
+                int(ip) & int(subnet)
+                for ip, subnet in zip(current_ip.split('.'), subnet_mask)
+            ]
+            if current_subnet == dut_subnet:
+                return current_ip
+        logging.error('No IP address found in requested subnet')
+
+    def setup_p2p_connection(self, testcase_params):
+        """Sets up WiFi Direct connection before running RvR."""
+
+        if self.testclass_params['p2p_group_negotiaton']:
+            self._setup_p2p_connection_negotiation(testcase_params)
+        else:
+            self._setup_p2p_connection_join_group(testcase_params)
+
+        # Get iperf server address
+        if wp2putils.is_go(self.android_devices[0]):
+            testcase_params['iperf_server_address'] = wp2putils.p2p_go_ip(
+                self.gc_dut)
+        else:
+            testcase_params['iperf_server_address'] = self._get_gc_ip()
+
+        p2p_interface = wp2putils.p2p_get_current_group(
+            self.gc_dut)['Interface']
+        connection_rssi = wputils.get_connected_rssi(self.gc_dut,
+                                                     interface=p2p_interface)
+        testcase_params['test_network'] = {'SSID': connection_rssi['ssid'][0]}
+        testcase_params['channel'] = wutils.WifiEnums.freq_to_channel[
+            connection_rssi['frequency'][0]]
+        if testcase_params['channel'] < 13:
+            testcase_params['mode'] = 'VHT20'
+        else:
+            testcase_params['mode'] = 'VHT80'
+        self.log.info('Wifi Direct Connection Established on Channel {} {} '
+                      '(SSID: {})'.format(
+                          testcase_params['channel'], testcase_params['mode'],
+                          testcase_params['test_network']['SSID']))
+
+    def setup_p2p_rvr_test(self, testcase_params):
+        # Setup the aps
+        self.setup_aps(testcase_params)
+        # Setup the duts
+        self.setup_duts(testcase_params)
+        # Set attenuator to 0 dB
+        for attenuator in self.attenuators:
+            attenuator.set_atten(0, strict=False)
+        # Setup the p2p connection
+        self.setup_p2p_connection(testcase_params)
+        # Set DUT to monitor RSSI and LLStats on
+        self.monitored_dut = self.gc_dut
+        self.monitored_interface = wp2putils.p2p_get_current_group(
+            self.gc_dut)['Interface']
+
+    def cleanup_p2p_rvr_test(self, testcase_params):
+        # clean-up
+        wp2putils.p2p_disconnect(self.go_dut)
+        wp2putils.check_disconnect(self.gc_dut)
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    def compile_test_params(self, testcase_params):
+        """Function that completes all test params based on the test name.
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Compile RvR parameters
+        num_atten_steps = int((self.testclass_params['atten_stop'] -
+                               self.testclass_params['atten_start']) /
+                              self.testclass_params['atten_step'])
+        testcase_params['atten_range'] = [
+            self.testclass_params['atten_start'] +
+            x * self.testclass_params['atten_step']
+            for x in range(0, num_atten_steps)
+        ]
+
+        # Compile iperf arguments
+        if testcase_params['traffic_type'] == 'TCP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'tcp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'tcp_processes', 1)
+        elif testcase_params['traffic_type'] == 'UDP':
+            testcase_params['iperf_socket_size'] = self.testclass_params.get(
+                'udp_socket_size', None)
+            testcase_params['iperf_processes'] = self.testclass_params.get(
+                'udp_processes', 1)
+        testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+            duration=self.testclass_params['iperf_duration'],
+            reverse_direction=(testcase_params['traffic_direction'] == 'DL'),
+            traffic_type=testcase_params['traffic_type'],
+            socket_size=testcase_params['iperf_socket_size'],
+            num_processes=testcase_params['iperf_processes'],
+            ipv6=False)
+        testcase_params['use_client_output'] = (
+            testcase_params['traffic_direction'] == 'DL')
+
+        # Compile AP and infrastructure connection parameters
+        ap_networks = []
+        if testcase_params['dut_connected'][0]:
+            band = testcase_params['dut_connected'][0].split('_')[0]
+            ap_networks.append({
+                'ap_id':
+                0,
+                'interface_id':
+                band if band == '2G' else band + '_1',
+                'band':
+                band,
+                'channel':
+                1 if band == '2G' else 36,
+                'connected_dut': [0]
+            })
+
+        if testcase_params['dut_connected'][1]:
+            if testcase_params['dut_connected'][0] == testcase_params[
+                    'dut_connected'][1]:
+                # if connected to same network, add it to the above
+                ap_networks[0]['connected_dut'].append(1)
+            else:
+                band = testcase_params['dut_connected'][1].split('_')[0]
+                if not testcase_params['dut_connected'][0]:
+                    # if it is the only dut connected, assign it to ap 0
+                    ap_id = 0
+                elif band == ap_networks[0]['band']:
+                    # if its connected to same band, connect to ap 1
+                    ap_id = 1
+                else:
+                    # if its on a different band, connect to ap 0 as well
+                    ap_id = 1
+                ap_networks.append({
+                    'ap_id':
+                    ap_id,
+                    'interface_id':
+                    band if band == '2G' else band + '_1',
+                    'band':
+                    band,
+                    'channel':
+                    11 if band == '2G' else 149,
+                    'connected_dut': [1]
+                })
+        testcase_params['ap_networks'] = ap_networks
+
+        return testcase_params
+
+    def _test_p2p_rvr(self, testcase_params):
+        """ Function that gets called for each test case
+
+        Args:
+            testcase_params: dict containing test-specific parameters
+        """
+        # Compile test parameters from config and test name
+        testcase_params = self.compile_test_params(testcase_params)
+
+        # Prepare devices and run test
+        self.setup_p2p_rvr_test(testcase_params)
+        rvr_result = self.run_rvr_test(testcase_params)
+        self.cleanup_p2p_rvr_test(testcase_params)
+
+        # Post-process results
+        self.testclass_results.append(rvr_result)
+        self.process_test_results(rvr_result)
+        self.pass_fail_check(rvr_result)
+
+
+class WifiP2pRvr_TCP_Test(WifiP2pRvrTest):
+    #Test cases
+    def test_p2p_rvr_TCP_DL_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=[False, False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_disconnected_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=[False, '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_disconnected_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=[False, '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_5G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', '2G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_DL_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', '5G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=[False, False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_disconnected_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=[False, '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_disconnected_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=[False, '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_5G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', '2G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_TCP_UL_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='TCP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', '5G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+
+class WifiP2pRvr_UDP_Test(WifiP2pRvrTest):
+    #Test cases
+    def test_p2p_rvr_UDP_DL_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=[False, False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_disconnected_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=[False, '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_disconnected_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=[False, '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_5G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['2G_1', '2G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_DL_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='DL',
+            dut_connected=['5G_1', '5G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_disconnected_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=[False, False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_2G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_5G_1_disconnected(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', False],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_disconnected_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=[False, '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_disconnected_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=[False, '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_2G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_5G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_2G_1_connected_5G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', '5G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_5G_1_connected_2G_1(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', '2G_1'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_2G_1_connected_2G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['2G_1', '2G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
+
+    def test_p2p_rvr_UDP_UL_connected_5G_1_connected_5G_2(self):
+        testcase_params = collections.OrderedDict(
+            traffic_type='UDP',
+            traffic_direction='UL',
+            dut_connected=['5G_1', '5G_2'],
+        )
+        self._test_p2p_rvr(testcase_params)
diff --git a/acts_tests/tests/google/wifi/rtt/README.md b/acts_tests/tests/google/wifi/rtt/README.md
new file mode 100644
index 0000000..1e435eb
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/README.md
@@ -0,0 +1,59 @@
+# Wi-Fi RTT (IEEE 802.11mc) Integrated (ACTS/sl4a) Test Suite
+
+This directory contains ACTS/sl4a test scripts to verify and characterize
+the Wi-Fi RTT (IEEE 802.11mc) implementation in Android.
+
+There are 2 groups of tests (in 2 sub-directories):
+
+* functional: Functional tests that each implementation must pass. These
+are pass/fail tests.
+* stress: Tests which run through a large number of iterations to stress
+test the implementation. Considering that some failures are expected,
+especially in an over-the-air situation, pass/fail criteria are either
+not provided or may not apply to all implementations or test environments.
+
+The tests can be executed using:
+
+`act.py -c <config> -tc {<test_class>|<test_class>:<test_name>}`
+
+Where a test file is any of the `.py` files in any of the test sub-directories.
+If a test class is specified, then all tests within that test class are executed.
+
+## Test Beds
+The Wi-Fi RTT tests support several different test scenarios which require different test bed
+configuration. The test beds and their corresponding test files are:
+
+* Device Under Test + AP which supports IEEE 802.11mc
+  * functional/RangeApSupporting11McTest.py
+  * functional/RttRequestManagementTest.py
+  * functional/RttDisableTest.py
+  * stress/StressRangeApTest.py
+* Device Under Test + AP which does **not** support IEEE 802.11mc
+  * functional/RangeApNonSupporting11McTest.py
+* 2 Devices Under Test
+  * functional/RangeAwareTest.py
+  * functional/AwareDiscoveryWithRangingTest.py
+  * functional/RangeSoftApTest.py
+  * stress/StressRangeAwareTest.py
+
+## Test Configurations
+The test configuration, the `<config>` in the commands above, is stored in
+the *config* sub-directory. The configuration simply uses all connected
+devices without listing specific serial numbers. Note that some tests use a
+single device while others use 2 devices.
+
+The only provided configuration is *wifi_rtt.json*.
+
+The configuration defines the following keys to configure the test:
+
+* **lci_reference**, **lcr_reference**: Arrays of bytes used to validate that the *correct* LCI and
+LCR were received from the AP. These are empty by default and should be configured to match the
+configuration of the AP used in the test.
+* **rtt_reference_distance_mm**: The reference distance, in mm, between the test device and the test
+AP or between the two test devices (for Aware ranging tests).
+* **stress_test_min_iteration_count**, **stress_test_target_run_time_sec**: Parameters used to
+control the length and duration of the stress tests. The stress test runs for the specified number
+of iterations or for the specified duration - whichever is longer.
+* **dbs_supported_models**: A list of device models which support DBS. Used to determine whether
+RTT will run while a SoftAP (SAP) is enabled. The model name corresponds to the value returned by
+*android_device.model*.
diff --git a/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json b/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
new file mode 100644
index 0000000..1870fe9
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
@@ -0,0 +1,21 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi RTT tests.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi RTT testbed: auto-detect all attached devices",
+            "name": "WifiRttAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts_tests/tests/google/wifi"],
+    "adb_logcat_param": "-b all",
+    "aware_default_power_mode": "INTERACTIVE",
+    "lci_reference": [],
+    "lcr_reference": [],
+    "rtt_reference_distance_mm": 100,
+    "stress_test_min_iteration_count": 100,
+    "stress_test_target_run_time_sec" : 30,
+    "dbs_supported_models" : []
+}
diff --git a/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py b/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
new file mode 100644
index 0000000..da0ba4b
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
@@ -0,0 +1,1928 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import sys
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class AwareDiscoveryWithRangingTest(AwareBaseTest, RttBaseTest):
+    """Set of tests for Wi-Fi Aware discovery configured with ranging (RTT)."""
+
+    SERVICE_NAME = "GoogleTestServiceRRRRR"
+
+    # Flag indicating whether the device has a limitation that does not allow it
+    # to execute Aware-based Ranging (whether direct or as part of discovery)
+    # whenever NDP is enabled.
+    RANGING_NDP_CONCURRENCY_LIMITATION = True
+
+    # Flag indicating whether the device has a limitation that does not allow it
+    # to execute Aware-based Ranging (whether direct or as part of discovery)
+    # for both Initiators and Responders. Only the first mode works.
+    RANGING_INITIATOR_RESPONDER_CONCURRENCY_LIMITATION = True
+
+    def setup_test(self):
+        """Manual setup here due to multiple inheritance: explicitly execute the
+    setup method from both parents."""
+        AwareBaseTest.setup_test(self)
+        RttBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        """Manual teardown here due to multiple inheritance: explicitly execute the
+    teardown method from both parents."""
+        AwareBaseTest.teardown_test(self)
+        RttBaseTest.teardown_test(self)
+
+    #########################################################################
+
+    def run_discovery(self,
+                      p_config,
+                      s_config,
+                      expect_discovery,
+                      expect_range=False):
+        """Run discovery on the 2 input devices with the specified configurations.
+
+    Args:
+      p_config, s_config: Publisher and Subscriber discovery configuration.
+      expect_discovery: True or False indicating whether discovery is expected
+                        with the specified configurations.
+      expect_range: True if we expect distance results (i.e. ranging to happen).
+                    Only relevant if expect_discovery is True.
+    Returns:
+      p_dut, s_dut: Publisher/Subscribe DUT
+      p_disc_id, s_disc_id: Publisher/Subscribe discovery session ID
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: start publish and wait for confirmation
+        p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Subscriber: start subscribe and wait for confirmation
+        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        # Subscriber: wait or fail on service discovery
+        if expect_discovery:
+            event = autils.wait_for_event(
+                s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+            if expect_range:
+                asserts.assert_true(
+                    aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                    "Discovery with ranging expected!")
+            else:
+                asserts.assert_false(
+                    aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                    "Discovery with ranging NOT expected!")
+        else:
+            autils.fail_on_event(s_dut,
+                                 aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+        # (single) sleep for timeout period and then verify that no further events
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+        return p_dut, s_dut, p_disc_id, s_disc_id
+
+    def run_discovery_update(self,
+                             p_dut,
+                             s_dut,
+                             p_disc_id,
+                             s_disc_id,
+                             p_config,
+                             s_config,
+                             expect_discovery,
+                             expect_range=False):
+        """Run discovery on the 2 input devices with the specified update
+    configurations. I.e. update the existing discovery sessions with the
+    configurations.
+
+    Args:
+      p_dut, s_dut: Publisher/Subscriber DUTs.
+      p_disc_id, s_disc_id: Publisher/Subscriber discovery session IDs.
+      p_config, s_config: Publisher and Subscriber discovery configuration.
+      expect_discovery: True or False indicating whether discovery is expected
+                        with the specified configurations.
+      expect_range: True if we expect distance results (i.e. ranging to happen).
+                    Only relevant if expect_discovery is True.
+    """
+
+        # try to perform reconfiguration at same time (and wait once for all
+        # confirmations)
+        if p_config is not None:
+            p_dut.droid.wifiAwareUpdatePublish(p_disc_id, p_config)
+        if s_config is not None:
+            s_dut.droid.wifiAwareUpdateSubscribe(s_disc_id, s_config)
+
+        if p_config is not None:
+            autils.wait_for_event(p_dut,
+                                  aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)
+        if s_config is not None:
+            autils.wait_for_event(s_dut,
+                                  aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)
+
+        # Subscriber: wait or fail on service discovery
+        if expect_discovery:
+            event = autils.wait_for_event(
+                s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+            if expect_range:
+                asserts.assert_true(
+                    aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                    "Discovery with ranging expected!")
+            else:
+                asserts.assert_false(
+                    aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                    "Discovery with ranging NOT expected!")
+        else:
+            autils.fail_on_event(s_dut,
+                                 aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+        # (single) sleep for timeout period and then verify that no further events
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+    def run_discovery_prange_sminmax_outofrange(self, is_unsolicited_passive):
+        """Run discovery with ranging:
+    - Publisher enables ranging
+    - Subscriber enables ranging with min/max such that out of range (min=large,
+      max=large+1)
+
+    Expected: no discovery
+
+    This is a baseline test for the update-configuration tests.
+
+    Args:
+      is_unsolicited_passive: True for Unsolicited/Passive, False for
+                              Solicited/Active.
+    Returns: the return arguments of the run_discovery.
+    """
+        pub_type = (aconsts.PUBLISH_TYPE_UNSOLICITED if is_unsolicited_passive
+                    else aconsts.PUBLISH_TYPE_SOLICITED)
+        sub_type = (aconsts.SUBSCRIBE_TYPE_PASSIVE if is_unsolicited_passive
+                    else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+        return self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME, pub_type, ssi=self.getname(2)),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME, sub_type, ssi=self.getname(2)),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001),
+            expect_discovery=False)
+
+    def getname(self, level=1):
+        """Python magic to return the name of the *calling* function.
+
+    Args:
+      level: How many levels up to go for the method name. Default = calling
+             method.
+    """
+        return sys._getframe(level).f_code.co_name
+
+    #########################################################################
+    # Run discovery with ranging configuration.
+    #
+    # Names: test_ranged_discovery_<ptype>_<stype>_<p_range>_<s_range>_<ref_dist>
+    #
+    # where:
+    # <ptype>_<stype>: unsolicited_passive or solicited_active
+    # <p_range>: prange or pnorange
+    # <s_range>: smin or smax or sminmax or snorange
+    # <ref_distance>: inrange or outoforange
+    #########################################################################
+
+    @test_tracker_info(uuid="3a216e9a-7a57-4741-89c0-84456975e1ac")
+    def test_ranged_discovery_unsolicited_passive_prange_snorange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber disables ranging
+
+    Expect: normal discovery (as if no ranging performed) - no distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                ssi=self.getname()),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="859a321e-18e2-437b-aa7a-2a45a42ee737")
+    def test_ranged_discovery_solicited_active_prange_snorange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber disables ranging
+
+    Expect: normal discovery (as if no ranging performed) - no distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                ssi=self.getname()),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="12a4f899-4f70-4641-8f3c-351004669b71")
+    def test_ranged_discovery_unsolicited_passive_pnorange_smax_inrange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher disables ranging
+    - Subscriber enables ranging with max such that always within range (large
+      max)
+
+    Expect: normal discovery (as if no ranging performed) - no distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=False),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=None,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="b7f90793-113d-4355-be20-856d92ac939f")
+    def test_ranged_discovery_solicited_active_pnorange_smax_inrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher disables ranging
+    - Subscriber enables ranging with max such that always within range (large
+      max)
+
+    Expect: normal discovery (as if no ranging performed) - no distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=False),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=None,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="da3ab6df-58f9-44ae-b7be-8200d9e1bb76")
+    def test_ranged_discovery_unsolicited_passive_pnorange_smin_outofrange(
+            self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher disables ranging
+    - Subscriber enables ranging with min such that always out of range (large
+      min)
+
+    Expect: normal discovery (as if no ranging performed) - no distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=False),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=None),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="275e0806-f266-4fa6-9ca0-1cfd7b65a6ca")
+    def test_ranged_discovery_solicited_active_pnorange_smin_outofrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher disables ranging
+    - Subscriber enables ranging with min such that always out of range (large
+      min)
+
+    Expect: normal discovery (as if no ranging performed) - no distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=False),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=None),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="8cd0aa1e-6866-4a5d-a550-f25483eebea1")
+    def test_ranged_discovery_unsolicited_passive_prange_smin_inrange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min such that in range (min=0)
+
+    Expect: discovery with distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=None),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="97c22c54-669b-4f7a-bf51-2f484e5f3e74")
+    def test_ranged_discovery_unsolicited_passive_prange_smax_inrange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with max such that in range (max=large)
+
+    Expect: discovery with distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=None,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="616673d7-9d0b-43de-a378-e5e949b51b32")
+    def test_ranged_discovery_unsolicited_passive_prange_sminmax_inrange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min/max such that in range (min=0,
+      max=large)
+
+    Expect: discovery with distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="2bf84912-dcad-4a8f-971f-e445a07f05ce")
+    def test_ranged_discovery_solicited_active_prange_smin_inrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min such that in range (min=0)
+
+    Expect: discovery with distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=None),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="5cfd7961-9665-4742-a1b5-2d1fc97f9795")
+    def test_ranged_discovery_solicited_active_prange_smax_inrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with max such that in range (max=large)
+
+    Expect: discovery with distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=None,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="5cf650ad-0b42-4b7d-9e05-d5f45fe0554d")
+    def test_ranged_discovery_solicited_active_prange_sminmax_inrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min/max such that in range (min=0,
+      max=large)
+
+    Expect: discovery with distance
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="5277f418-ac35-43ce-9b30-3c895272898e")
+    def test_ranged_discovery_unsolicited_passive_prange_smin_outofrange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min such that out of range (min=large)
+
+    Expect: no discovery
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=None),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="8a7e6ab1-acf4-41a7-a5fb-8c164d593b5f")
+    def test_ranged_discovery_unsolicited_passive_prange_smax_outofrange(self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with max such that in range (max=0)
+
+    Expect: no discovery
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=None,
+                max_distance_mm=0),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="b744f5f9-2641-4373-bf86-3752e2f9aace")
+    def test_ranged_discovery_unsolicited_passive_prange_sminmax_outofrange(
+            self):
+        """Verify discovery with ranging:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min/max such that out of range (min=large,
+      max=large+1)
+
+    Expect: no discovery
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="d2e94199-b2e6-4fa5-a347-24594883c801")
+    def test_ranged_discovery_solicited_active_prange_smin_outofrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min such that out of range (min=large)
+
+    Expect: no discovery
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=None),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="a5619835-496a-4244-a428-f85cba3d4115")
+    def test_ranged_discovery_solicited_active_prange_smax_outofrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with max such that out of range (max=0)
+
+    Expect: no discovery
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=None,
+                max_distance_mm=0),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="12ebd91f-a973-410b-8ee1-0bd86024b921")
+    def test_ranged_discovery_solicited_active_prange_sminmax_outofrange(self):
+        """Verify discovery with ranging:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber enables ranging with min/max such that out of range (min=large,
+      max=large+1)
+
+    Expect: no discovery
+    """
+        self.run_discovery(
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001),
+            expect_discovery=False)
+
+    #########################################################################
+    # Run discovery with ranging configuration & update configurations after
+    # first run.
+    #
+    # Names: test_ranged_updated_discovery_<ptype>_<stype>_<scenario>
+    #
+    # where:
+    # <ptype>_<stype>: unsolicited_passive or solicited_active
+    # <scenario>: test scenario (details in name)
+    #########################################################################
+
+    @test_tracker_info(uuid="59442180-4a6c-428f-b926-86000e8339b4")
+    def test_ranged_updated_discovery_unsolicited_passive_oor_to_ir(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber:
+      - Starts: Ranging enabled, min/max such that out of range (min=large,
+                max=large+1)
+      - Reconfigured to: Ranging enabled, min/max such that in range (min=0,
+                        max=large)
+
+    Expect: discovery + ranging after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="60188508-104d-42d5-ac3a-3605093c45d7")
+    def test_ranged_updated_discovery_unsolicited_passive_pub_unrange(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber: Ranging enabled, min/max such that out of range (min=large,
+                  max=large+1)
+    - Reconfigured to: Publisher disables ranging
+
+    Expect: discovery w/o ranging after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.PUBLISH_TYPE_UNSOLICITED,
+                ssi=self.getname()),
+            s_config=None,  # no updates
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="f96b434e-751d-4eb5-ae01-0c5c3a6fb4a2")
+    def test_ranged_updated_discovery_unsolicited_passive_sub_unrange(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber:
+      - Starts: Ranging enabled, min/max such that out of range (min=large,
+                max=large+1)
+      - Reconfigured to: Ranging disabled
+
+    Expect: discovery w/o ranging after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                ssi=self.getname()),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="78970de8-9362-4647-931a-3513bcf58e80")
+    def test_ranged_updated_discovery_unsolicited_passive_sub_oor(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber:
+      - Starts: Ranging enabled, min/max such that out of range (min=large,
+                max=large+1)
+      - Reconfigured to: different out-of-range setting
+
+    Expect: no discovery after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=100000,
+                max_distance_mm=100001),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="0841ad05-4899-4521-bd24-04a8e2e345ac")
+    def test_ranged_updated_discovery_unsolicited_passive_pub_same(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber: Ranging enabled, min/max such that out of range (min=large,
+                  max=large+1)
+    - Reconfigured to: Publisher with same settings (ranging enabled)
+
+    Expect: no discovery after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_UNSOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=None,  # no updates
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="ec6ca57b-f115-4516-813a-4572b930c8d3")
+    def test_ranged_updated_discovery_unsolicited_passive_multi_step(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber: Ranging enabled, min/max such that out of range (min=large,
+                  max=large+1)
+      - Expect: no discovery
+    - Reconfigured to: Ranging enabled, min/max such that in-range (min=0)
+      - Expect: discovery with ranging
+    - Reconfigured to: Ranging enabled, min/max such that out-of-range
+                       (min=large)
+      - Expect: no discovery
+    - Reconfigured to: Ranging disabled
+      - Expect: discovery without ranging
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=None),
+            expect_discovery=True,
+            expect_range=True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=None),
+            expect_discovery=False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                ssi=self.getname()),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="bbaac63b-000c-415f-bf19-0906f04031cd")
+    def test_ranged_updated_discovery_solicited_active_oor_to_ir(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber:
+      - Starts: Ranging enabled, min/max such that out of range (min=large,
+                max=large+1)
+      - Reconfigured to: Ranging enabled, min/max such that in range (min=0,
+                        max=large)
+
+    Expect: discovery + ranging after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=1000000),
+            expect_discovery=True,
+            expect_range=True)
+
+    @test_tracker_info(uuid="c385b361-7955-4f34-9109-8d8ca81cb4cc")
+    def test_ranged_updated_discovery_solicited_active_pub_unrange(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber: Ranging enabled, min/max such that out of range (min=large,
+                  max=large+1)
+    - Reconfigured to: Publisher disables ranging
+
+    Expect: discovery w/o ranging after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.PUBLISH_TYPE_SOLICITED,
+                ssi=self.getname()),
+            s_config=None,  # no updates
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="ec5120ea-77ec-48c6-8820-48b82ad3dfd4")
+    def test_ranged_updated_discovery_solicited_active_sub_unrange(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber:
+      - Starts: Ranging enabled, min/max such that out of range (min=large,
+                max=large+1)
+      - Reconfigured to: Ranging disabled
+
+    Expect: discovery w/o ranging after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                ssi=self.getname()),
+            expect_discovery=True,
+            expect_range=False)
+
+    @test_tracker_info(uuid="6231cb42-91e4-48d3-b9db-b37efbe8537c")
+    def test_ranged_updated_discovery_solicited_active_sub_oor(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber:
+      - Starts: Ranging enabled, min/max such that out of range (min=large,
+                max=large+1)
+      - Reconfigured to: different out-of-range setting
+
+    Expect: no discovery after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=100000,
+                max_distance_mm=100001),
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="ec999420-6a50-455e-b624-f4c9b4cb7ea5")
+    def test_ranged_updated_discovery_solicited_active_pub_same(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Solicited Publish/Active Subscribe
+    - Publisher enables ranging
+    - Subscriber: Ranging enabled, min/max such that out of range (min=large,
+                  max=large+1)
+    - Reconfigured to: Publisher with same settings (ranging enabled)
+
+    Expect: no discovery after update
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.PUBLISH_TYPE_SOLICITED,
+                    ssi=self.getname()),
+                enable_ranging=True),
+            s_config=None,  # no updates
+            expect_discovery=False)
+
+    @test_tracker_info(uuid="3938a3dc-8032-4096-b184-b528e4564b5e")
+    def test_ranged_updated_discovery_solicited_active_multi_step(self):
+        """Verify discovery with ranging operation with updated configuration:
+    - Unsolicited Publish/Passive Subscribe
+    - Publisher enables ranging
+    - Subscriber: Ranging enabled, min/max such that out of range (min=large,
+                  max=large+1)
+      - Expect: no discovery
+    - Reconfigured to: Ranging enabled, min/max such that in-range (min=0)
+      - Expect: discovery with ranging
+    - Reconfigured to: Ranging enabled, min/max such that out-of-range
+                       (min=large)
+      - Expect: no discovery
+    - Reconfigured to: Ranging disabled
+      - Expect: discovery without ranging
+    """
+        (p_dut, s_dut, p_disc_id,
+         s_disc_id) = self.run_discovery_prange_sminmax_outofrange(True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=0,
+                max_distance_mm=None),
+            expect_discovery=True,
+            expect_range=True)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.add_ranging_to_sub(
+                autils.create_discovery_config(
+                    self.SERVICE_NAME,
+                    aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                    ssi=self.getname()),
+                min_distance_mm=1000000,
+                max_distance_mm=None),
+            expect_discovery=False)
+        self.run_discovery_update(
+            p_dut,
+            s_dut,
+            p_disc_id,
+            s_disc_id,
+            p_config=None,  # no updates
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME,
+                aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                ssi=self.getname()),
+            expect_discovery=True,
+            expect_range=False)
+
+    #########################################################################
+
+    @test_tracker_info(uuid="6edc47ab-7300-4bff-b7dd-5de83f58928a")
+    def test_ranged_discovery_multi_session(self):
+        """Verify behavior with multiple concurrent discovery session with different
+    configurations:
+
+    Device A (Publisher):
+      Publisher AA: ranging enabled
+      Publisher BB: ranging enabled
+      Publisher CC: ranging enabled
+      Publisher DD: ranging disabled
+    Device B (Subscriber):
+      Subscriber AA: ranging out-of-range -> no match
+      Subscriber BB: ranging in-range -> match w/range
+      Subscriber CC: ranging disabled -> match w/o range
+      Subscriber DD: ranging out-of-range -> match w/o range
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Subscriber: start sessions
+        aa_s_disc_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("AA",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001), True)
+        bb_s_disc_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("BB",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=0,
+                max_distance_mm=1000000), True)
+        cc_s_disc_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.create_discovery_config(
+                "CC", aconsts.SUBSCRIBE_TYPE_PASSIVE), True)
+        dd_s_disc_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("DD",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001), True)
+
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  aa_s_disc_id))
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  bb_s_disc_id))
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  cc_s_disc_id))
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  dd_s_disc_id))
+
+        # Publisher: start sessions
+        aa_p_disc_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "AA", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        bb_p_disc_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "BB", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        cc_p_disc_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "CC", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        dd_p_disc_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.create_discovery_config(
+                "DD", aconsts.PUBLISH_TYPE_UNSOLICITED), True)
+
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  aa_p_disc_id))
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  bb_p_disc_id))
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  cc_p_disc_id))
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  dd_p_disc_id))
+
+        # Expected and unexpected service discovery
+        event = autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  bb_s_disc_id))
+        asserts.assert_true(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for BB expected!")
+        event = autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  cc_s_disc_id))
+        asserts.assert_false(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for CC NOT expected!")
+        event = autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  dd_s_disc_id))
+        asserts.assert_false(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for DD NOT expected!")
+        autils.fail_on_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  aa_s_disc_id))
+
+        # (single) sleep for timeout period and then verify that no further events
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+    #########################################################################
+
+    @test_tracker_info(uuid="deede47f-a54c-46d9-88bb-f4482fbd8470")
+    def test_ndp_concurrency(self):
+        """Verify the behavior of Wi-Fi Aware Ranging whenever an NDP is created -
+    for those devices that have a concurrency limitation that does not allow
+    Aware Ranging, whether direct or as part of discovery.
+
+    Publisher: start 3 services
+      AA w/o ranging
+      BB w/ ranging
+      CC w/ ranging
+      DD w/ ranging
+    Subscriber: start 2 services
+      AA w/o ranging
+      BB w/ ranging out-of-range
+      (do not start CC!)
+      DD w/ ranging in-range
+    Expect AA discovery, DD discovery w/range, but no BB
+    Start NDP in context of AA
+    IF NDP_CONCURRENCY_LIMITATION:
+      Verify discovery on BB w/o range
+    Start EE w/ranging out-of-range
+    Start FF w/ranging in-range
+    IF NDP_CONCURRENCY_LIMITATION:
+      Verify discovery on EE w/o range
+      Verify discovery on FF w/o range
+    Else:
+      Verify discovery on FF w/ range
+    Tear down NDP
+    Subscriber
+      Start CC w/ ranging out-of-range
+      Wait to verify that do not get match
+      Update configuration to be in-range
+      Verify that get match with ranging information
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False)
+        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: AA w/o ranging, BB w/ ranging, CC w/ ranging, DD w/ ranging
+        aa_p_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.create_discovery_config(
+                "AA", aconsts.PUBLISH_TYPE_SOLICITED), True)
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  aa_p_id))
+        bb_p_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "BB", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  bb_p_id))
+        cc_p_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "CC", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  cc_p_id))
+        dd_p_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "DD", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  dd_p_id))
+
+        # Subscriber: AA w/o ranging, BB w/ranging out-of-range,
+        #             DD w /ranging in-range
+        aa_s_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.create_discovery_config(
+                "AA", aconsts.SUBSCRIBE_TYPE_ACTIVE), True)
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  aa_s_id))
+        bb_s_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("BB",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001), True)
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  bb_s_id))
+        dd_s_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("DD",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  dd_s_id))
+
+        # verify: AA discovered, BB not discovered, DD discovery w/range
+        event = autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  aa_s_id))
+        asserts.assert_false(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for AA NOT expected!")
+        aa_peer_id_on_sub = event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+        autils.fail_on_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  bb_s_id))
+        event = autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  dd_s_id))
+        asserts.assert_true(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for DD expected!")
+
+        # start NDP in context of AA:
+
+        # Publisher: request network (from ANY)
+        p_req_key = autils.request_network(
+            p_dut, p_dut.droid.wifiAwareCreateNetworkSpecifier(aa_p_id, None))
+
+        # Subscriber: request network
+        s_req_key = autils.request_network(
+            s_dut,
+            s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                aa_s_id, aa_peer_id_on_sub))
+
+        # Publisher & Subscriber: wait for network formation
+        p_net_event = autils.wait_for_event_with_keys(
+            p_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+        s_net_event = autils.wait_for_event_with_keys(
+            s_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+        p_aware_if = p_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        s_aware_if = s_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+        p_ipv6 = p_dut.droid.connectivityGetLinkLocalIpv6Address(
+            p_aware_if).split("%")[0]
+        s_ipv6 = s_dut.droid.connectivityGetLinkLocalIpv6Address(
+            s_aware_if).split("%")[0]
+
+        self.log.info("AA NDP Interface names: P=%s, S=%s", p_aware_if,
+                      s_aware_if)
+        self.log.info("AA NDP Interface addresses (IPv6): P=%s, S=%s", p_ipv6,
+                      s_ipv6)
+
+        if self.RANGING_NDP_CONCURRENCY_LIMITATION:
+            # Expect BB to now discover w/o ranging
+            event = autils.wait_for_event(
+                s_dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                      bb_s_id))
+            asserts.assert_false(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for BB NOT expected!")
+
+        # Publishers: EE, FF w/ ranging
+        ee_p_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config("EE",
+                                               aconsts.PUBLISH_TYPE_SOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  ee_p_id))
+        ff_p_id = p_dut.droid.wifiAwarePublish(
+            p_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "FF", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            p_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  ff_p_id))
+
+        # Subscribers: EE out-of-range, FF in-range
+        ee_s_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("EE",
+                                               aconsts.SUBSCRIBE_TYPE_ACTIVE),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001), True)
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  ee_s_id))
+        ff_s_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("FF",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  ff_s_id))
+
+        if self.RANGING_NDP_CONCURRENCY_LIMITATION:
+            # Expect EE & FF discovery w/o range
+            event = autils.wait_for_event(
+                s_dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                      ee_s_id))
+            asserts.assert_false(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for EE NOT expected!")
+            event = autils.wait_for_event(
+                s_dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                      ff_s_id))
+            asserts.assert_false(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for FF NOT expected!")
+        else:
+            event = autils.wait_for_event(
+                s_dut,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                      ff_s_id))
+            asserts.assert_true(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for FF expected!")
+
+        # tear down NDP
+        p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+        s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+        time.sleep(5)  # give time for NDP termination to finish
+
+        # Subscriber: start CC out-of-range - no discovery expected!
+        cc_s_id = s_dut.droid.wifiAwareSubscribe(
+            s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("CC",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001), True)
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  cc_s_id))
+        autils.fail_on_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  cc_s_id))
+
+        # Subscriber: modify CC to in-range - expect discovery w/ range
+        s_dut.droid.wifiAwareUpdateSubscribe(
+            cc_s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("CC",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000001))
+        autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
+                                  cc_s_id))
+        event = autils.wait_for_event(
+            s_dut,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  cc_s_id))
+        asserts.assert_true(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for CC expected!")
+
+    @test_tracker_info(uuid="d94dac91-4090-4c03-a867-6dfac6558ba3")
+    def test_role_concurrency(self):
+        """Verify the behavior of Wi-Fi Aware Ranging (in the context of discovery)
+     when the device has concurrency limitations which do not permit concurrent
+     Initiator and Responder roles on the same device. In such case it is
+     expected that normal discovery without ranging is executed AND that ranging
+     is restored whenever the concurrency constraints are removed.
+
+     Note: all Subscribers are in-range.
+
+     DUT1: start multiple services
+      Publish AA w/ ranging (unsolicited)
+      Subscribe BB w/ ranging (active)
+      Publish CC w/ ranging (unsolicited)
+      Publish DD w/o ranging (solicited)
+      Subscribe EE w/ ranging (passive)
+      Subscribe FF w/ ranging (active)
+     DUT2: start multiple services
+      Subscribe AA w/ ranging (passive)
+      Publish BB w/ ranging (solicited)
+      Subscribe DD w/o ranging (active)
+     Expect
+      DUT2: AA match w/ range information
+      DUT1: BB match w/o range information (concurrency disables ranging)
+      DUT2: DD match w/o range information
+     DUT1: Terminate AA
+     DUT2:
+      Terminate AA
+      Start Publish EE w/ ranging (unsolicited)
+     DUT1: expect EE w/o ranging
+     DUT1: Terminate CC
+     DUT2: Start Publish FF w/ ranging (solicited)
+     DUT1: expect FF w/ ranging information - should finally be back up
+     """
+        dut1 = self.android_devices[0]
+        dut1.pretty_name = "DUT1"
+        dut2 = self.android_devices[1]
+        dut2.pretty_name = "DUT2"
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        dut1_id = dut1.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut1, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        dut2_id = dut2.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut2, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # DUT1: initial service bringup
+        aa_p_id = dut1.droid.wifiAwarePublish(
+            dut1_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "AA", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  aa_p_id))
+        bb_s_id = dut1.droid.wifiAwareSubscribe(
+            dut1_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("BB",
+                                               aconsts.SUBSCRIBE_TYPE_ACTIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  bb_s_id))
+        cc_p_id = dut1.droid.wifiAwarePublish(
+            dut1_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "CC", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  cc_p_id))
+        dd_p_id = dut1.droid.wifiAwarePublish(
+            dut1_id,
+            autils.create_discovery_config(
+                "DD", aconsts.PUBLISH_TYPE_SOLICITED), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  dd_p_id))
+        ee_s_id = dut1.droid.wifiAwareSubscribe(
+            dut1_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("EE",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  ee_s_id))
+        ff_s_id = dut1.droid.wifiAwareSubscribe(
+            dut1_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("FF",
+                                               aconsts.SUBSCRIBE_TYPE_ACTIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  ff_s_id))
+
+        # DUT2: initial service bringup
+        aa_s_id = dut2.droid.wifiAwareSubscribe(
+            dut2_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("AA",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  aa_s_id))
+        bb_p_id = dut2.droid.wifiAwarePublish(
+            dut2_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config("BB",
+                                               aconsts.PUBLISH_TYPE_SOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  bb_p_id))
+        dd_s_id = dut2.droid.wifiAwareSubscribe(
+            dut2_id,
+            autils.create_discovery_config(
+                "AA", aconsts.SUBSCRIBE_TYPE_ACTIVE), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  dd_s_id))
+
+        # Initial set of discovery events for AA, BB, and DD (which are up)
+        event = autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  aa_s_id))
+        asserts.assert_true(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for AA expected!")
+        event = autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  bb_s_id))
+        if self.RANGING_INITIATOR_RESPONDER_CONCURRENCY_LIMITATION:
+            asserts.assert_false(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for BB NOT expected!")
+        else:
+            asserts.assert_true(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for BB expected!")
+        event = autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  dd_s_id))
+        asserts.assert_false(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for DD NOT expected!")
+
+        # DUT1/DUT2: terminate AA
+        dut1.droid.wifiAwareDestroyDiscoverySession(aa_p_id)
+        dut2.droid.wifiAwareDestroyDiscoverySession(aa_s_id)
+
+        time.sleep(
+            5)  # guarantee that session terminated (and host recovered?)
+
+        # DUT2: try EE service - ranging still disabled
+        ee_p_id = dut2.droid.wifiAwarePublish(
+            dut2_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "EE", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  ee_p_id))
+
+        event = autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  ee_s_id))
+        if self.RANGING_INITIATOR_RESPONDER_CONCURRENCY_LIMITATION:
+            asserts.assert_false(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for EE NOT expected!")
+        else:
+            asserts.assert_true(
+                aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+                "Discovery with ranging for EE expected!")
+
+        # DUT1: terminate CC - last publish w/ ranging on DUT!
+        dut1.droid.wifiAwareDestroyDiscoverySession(cc_p_id)
+
+        time.sleep(
+            5)  # guarantee that session terminated (and host recovered?)
+
+        # DUT2: try FF service - ranging should now function
+        ff_p_id = dut2.droid.wifiAwarePublish(
+            dut2_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config("FF",
+                                               aconsts.PUBLISH_TYPE_SOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  ff_p_id))
+
+        event = autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  ff_s_id))
+        asserts.assert_true(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for FF expected!")
+
+    @test_tracker_info(uuid="6700eab8-a172-43cd-aed3-e6577ce8fd89")
+    def test_discovery_direct_concurrency(self):
+        """Verify the behavior of Wi-Fi Aware Ranging used as part of discovery and
+    as direct ranging to a peer device.
+
+    Process:
+    - Start YYY service with ranging in-range
+    - Start XXX service with ranging out-of-range
+    - Start performing direct Ranging
+    - While above going on update XXX to be in-range
+    - Keep performing direct Ranging in context of YYY
+    - Stop direct Ranging and look for XXX to discover
+    """
+        dut1 = self.android_devices[0]
+        dut1.pretty_name = "DUT1"
+        dut2 = self.android_devices[1]
+        dut2.pretty_name = "DUT2"
+
+        # DUTs: attach and wait for confirmation
+        dut1_id = dut1.droid.wifiAwareAttach(False)
+        autils.wait_for_event(dut1, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        dut2_id = dut2.droid.wifiAwareAttach(True)
+        event = autils.wait_for_event(dut2,
+                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        dut2_mac = event['data']['mac']
+
+        # DUT1: publishers bring-up
+        xxx_p_id = dut1.droid.wifiAwarePublish(
+            dut1_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "XXX", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  xxx_p_id))
+        yyy_p_id = dut1.droid.wifiAwarePublish(
+            dut1_id,
+            autils.add_ranging_to_pub(
+                autils.create_discovery_config(
+                    "YYY", aconsts.PUBLISH_TYPE_UNSOLICITED),
+                enable_ranging=True), True)
+        autils.wait_for_event(
+            dut1,
+            autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
+                                  yyy_p_id))
+
+        # DUT2: subscribers bring-up
+        xxx_s_id = dut2.droid.wifiAwareSubscribe(
+            dut2_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("XXX",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=1000000,
+                max_distance_mm=1000001), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  xxx_s_id))
+        yyy_s_id = dut2.droid.wifiAwareSubscribe(
+            dut2_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("YYY",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000), True)
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                                  yyy_s_id))
+
+        # Service discovery: YYY (with range info), but no XXX
+        event = autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  yyy_s_id))
+        asserts.assert_true(
+            aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"],
+            "Discovery with ranging for YYY expected!")
+        yyy_peer_id_on_sub = event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        autils.fail_on_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                  xxx_s_id))
+
+        # Direct ranging
+        results21 = []
+        for iter in range(10):
+            id = dut2.droid.wifiRttStartRangingToAwarePeerId(
+                yyy_peer_id_on_sub)
+            event = autils.wait_for_event(
+                dut2,
+                rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT, id))
+            results21.append(
+                event["data"][rconsts.EVENT_CB_RANGING_KEY_RESULTS][0])
+
+        time.sleep(5)  # while switching roles
+
+        results12 = []
+        for iter in range(10):
+            id = dut1.droid.wifiRttStartRangingToAwarePeerMac(dut2_mac)
+            event = autils.wait_for_event(
+                dut1,
+                rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT, id))
+            results12.append(
+                event["data"][rconsts.EVENT_CB_RANGING_KEY_RESULTS][0])
+
+        stats = [
+            rutils.extract_stats(results12, 0, 0, 0),
+            rutils.extract_stats(results21, 0, 0, 0)
+        ]
+
+        # Update XXX to be within range
+        dut2.droid.wifiAwareUpdateSubscribe(
+            xxx_s_id,
+            autils.add_ranging_to_sub(
+                autils.create_discovery_config("XXX",
+                                               aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                min_distance_mm=None,
+                max_distance_mm=1000000))
+        autils.wait_for_event(
+            dut2,
+            autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
+                                  xxx_s_id))
+
+        # Expect discovery on XXX - wait until discovery with ranging:
+        # - 0 or more: without ranging info (due to concurrency limitations)
+        # - 1 or more: with ranging (once concurrency limitation relieved)
+        num_events = 0
+        while True:
+            event = autils.wait_for_event(
+                dut2,
+                autils.decorate_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                      xxx_s_id))
+            if aconsts.SESSION_CB_KEY_DISTANCE_MM in event["data"]:
+                break
+            num_events = num_events + 1
+            asserts.assert_true(
+                num_events < 10,  # arbitrary safety valve
+                "Way too many discovery events without ranging!")
+
+        asserts.explicit_pass(
+            "Discovery/Direct RTT Concurrency Pass", extras={"data": stats})
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py
new file mode 100644
index 0000000..c68ca92
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class RangeApMiscTest(RttBaseTest):
+    """Test class for RTT ranging to Access Points - miscellaneous tests which
+  do not fit into the strict IEEE 802.11mc supporting or non-supporting test
+  beds - e.g. a mixed test."""
+
+    # Number of RTT iterations
+    NUM_ITER = 10
+
+    # Time gap (in seconds) between iterations
+    TIME_BETWEEN_ITERATIONS = 0
+
+    #############################################################################
+
+    def test_rtt_mixed_80211mc_supporting_aps_wo_privilege(self):
+        """Scan for APs and perform RTT on one supporting and one non-supporting
+    IEEE 802.11mc APs with the device not having privilege access (expect
+    failures)."""
+        dut = self.android_devices[0]
+        rutils.config_privilege_override(dut, True)
+        rtt_aps = rutils.scan_with_rtt_support_constraint(dut, True)
+        non_rtt_aps = rutils.scan_with_rtt_support_constraint(dut, False)
+        mix_list = [rtt_aps[0], non_rtt_aps[0]]
+        dut.log.debug("Visible non-IEEE 802.11mc APs=%s", mix_list)
+        events = rutils.run_ranging(dut, mix_list, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=stats)
+            if bssid == rtt_aps[0][wutils.WifiEnums.BSSID_KEY]:
+                asserts.assert_false(
+                    stat['any_lci_mismatch'], "LCI mismatch", extras=stats)
+                asserts.assert_false(
+                    stat['any_lcr_mismatch'], "LCR mismatch", extras=stats)
+                asserts.assert_equal(
+                    stat['num_invalid_rssi'], 0, "Invalid RSSI", extras=stats)
+                asserts.assert_true(
+                    stat['num_failures'] <=
+                    self.rtt_max_failure_rate_two_sided_rtt_percentage *
+                    stat['num_results'] / 100,
+                    "Failure rate is too high",
+                    extras=stats)
+                asserts.assert_true(
+                    stat['num_range_out_of_margin'] <=
+                    self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage
+                    * stat['num_success_results'] / 100,
+                    "Results exceeding error margin rate is too high",
+                    extras=stats)
+            else:
+                asserts.assert_true(
+                    stat['num_failures'] == self.NUM_ITER,
+                    "All one-sided RTT requests must fail when executed without privilege",
+                    extras=stats)
+                for code in stat['status_codes']:
+                    asserts.assert_true(
+                        code == rconsts.
+                        EVENT_CB_RANGING_STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC,
+                        "Expected non-support error code",
+                        extras=stats)
+        asserts.explicit_pass("RTT test done", extras=stats)
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
new file mode 100644
index 0000000..a40647b
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
@@ -0,0 +1,177 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class RangeApNonSupporting11McTest(RttBaseTest, WifiBaseTest):
+    """Test class for RTT ranging to Access Points which do not support IEEE
+    802.11mc
+    """
+
+    # Number of RTT iterations
+    NUM_ITER = 10
+
+    # Time gap (in seconds) between iterations
+    TIME_BETWEEN_ITERATIONS = 0
+
+    def setup_class(self):
+        super().setup_class()
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+
+    def run_test_rtt_non_80211mc_supporting_aps(self, dut, accuracy_evaluation=False):
+        """Scan for APs and perform RTT on non-IEEE 802.11mc supporting APs
+        Args:
+            dut: test device
+            accuracy_evaluation: False - only evaluate success rate.
+                                 True - evaluate both success rate and accuracy
+                                 default is False.
+        """
+        asserts.skip_if(
+            not dut.rtt_capabilities[rconsts.CAP_RTT_ONE_SIDED_SUPPORTED],
+            "Device does not support one-sided RTT")
+
+        non_rtt_aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, False),
+            select_count=1)
+        dut.log.debug("Visible non-IEEE 802.11mc APs=%s", non_rtt_aps)
+        asserts.assert_true(len(non_rtt_aps) > 0, "Need at least one AP!")
+        events = rutils.run_ranging(dut, non_rtt_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       [], [])
+        dut.log.debug("Stats=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=stats)
+            asserts.assert_false(
+                stat['any_lci_mismatch'], "LCI mismatch", extras=stats)
+            asserts.assert_false(
+                stat['any_lcr_mismatch'], "LCR mismatch", extras=stats)
+            asserts.assert_equal(
+                stat['num_invalid_rssi'], 0, "Invalid RSSI", extras=stats)
+            asserts.assert_true(
+                stat['num_failures'] <=
+                self.rtt_max_failure_rate_one_sided_rtt_percentage *
+                stat['num_results'] / 100,
+                "Failure rate is too high",
+                extras=stats)
+            if accuracy_evaluation:
+                asserts.assert_true(
+                    stat['num_range_out_of_margin'] <=
+                    self.rtt_max_margin_exceeded_rate_one_sided_rtt_percentage *
+                    stat['num_success_results'] / 100,
+                    "Results exceeding error margin rate is too high",
+                    extras=stats)
+        asserts.explicit_pass("RTT test done", extras=stats)
+
+    @test_tracker_info(uuid="cde756e9-11f3-43da-b9ae-9edf85764f82")
+    def test_rtt_non_80211mc_supporting_aps(self):
+        """Scan for APs and perform RTT on non-IEEE 802.11mc supporting APs,
+        Functionality test: Only evaluate success rate.
+        """
+        dut = self.android_devices[0]
+        self.run_test_rtt_non_80211mc_supporting_aps(dut)
+
+    @test_tracker_info(uuid="8fea37f7-0499-4b02-bd33-5ae4d697a4b7")
+    def test_rtt_non_80211mc_supporting_aps_with_accuracy_evaluation(self):
+        """Scan for APs and perform RTT on non-IEEE 802.11mc supporting APs,
+        Performance test: evaluate success rate and accuracy.
+        """
+        dut = self.android_devices[0]
+        self.run_test_rtt_non_80211mc_supporting_aps(dut, accuracy_evaluation=True)
+
+    @test_tracker_info(uuid="c9e22185-16d4-4fe6-894f-5823587b3288")
+    def test_rtt_non_80211mc_supporting_aps_wo_privilege(self):
+        """Scan for APs and perform RTT on non-IEEE 802.11mc supporting APs with the
+        device not having privilege access (expect failures).
+        """
+        dut = self.android_devices[0]
+        rutils.config_privilege_override(dut, True)
+        non_rtt_aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, False),
+            select_count=1)
+        dut.log.debug("Visible non-IEEE 802.11mc APs=%s", non_rtt_aps)
+        asserts.assert_true(len(non_rtt_aps) > 0, "Need at least one AP!")
+        events = rutils.run_ranging(dut, non_rtt_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=stats)
+            asserts.assert_true(
+                stat['num_failures'] == self.NUM_ITER,
+                "All one-sided RTT requests must fail when executed without privilege",
+                extras=stats)
+            for code in stat['status_codes']:
+                asserts.assert_true(
+                    code == rconsts.
+                    EVENT_CB_RANGING_STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC,
+                    "Expected non-support error code",
+                    extras=stats)
+        asserts.explicit_pass("RTT test done", extras=stats)
+
+    @test_tracker_info(uuid="e117af56-bd3f-40ae-a2fd-4175f0daa7fa")
+    def test_rtt_non_80211mc_supporting_ap_faked_as_supporting(self):
+        """Scan for APs which do not support IEEE 802.11mc, maliciously modify the
+        Responder config to indicate support and pass-through to service. Verify
+        that get an error result.
+        """
+        dut = self.android_devices[0]
+        non_rtt_aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, False),
+            select_count=1)
+        dut.log.debug("Visible non-IEEE 802.11mc APs=%s", non_rtt_aps)
+        asserts.assert_true(len(non_rtt_aps) > 0, "Need at least one AP!")
+        non_rtt_aps = non_rtt_aps[0:1]  # pick first
+        non_rtt_aps[0][rconsts.SCAN_RESULT_KEY_RTT_RESPONDER] = True  # falsify
+        dut.log.debug("Visible non-IEEE 802.11mc APs=%s", non_rtt_aps)
+        events = rutils.run_ranging(dut, non_rtt_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=stats)
+            asserts.assert_true(
+                stat['num_failures'] == self.NUM_ITER,
+                "Failures expected for falsified responder config",
+                extras=stats)
+        asserts.explicit_pass("RTT test done", extras=stats)
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
new file mode 100644
index 0000000..c218898
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class RangeApSupporting11McTest(RttBaseTest):
+    """Test class for RTT ranging to Access Points which support IEEE 802.11mc"""
+
+    # Number of RTT iterations
+    NUM_ITER = 10
+
+    # Time gap (in seconds) between iterations
+    TIME_BETWEEN_ITERATIONS = 0
+
+    # Soft AP SSID
+    SOFT_AP_SSID = "RTT_TEST_SSID"
+
+    # Soft AP Password (irrelevant)
+    SOFT_AP_PASSWORD = "ABCDEFGH"
+
+    # Time to wait before configuration changes
+    WAIT_FOR_CONFIG_CHANGES_SEC = 1
+
+    def run_test_rtt_80211mc_supporting_aps(self, dut, accuracy_evaluation=False):
+        """Scan for APs and perform RTT only to those which support 802.11mc
+        Args:
+            dut: test device
+            accuracy_evaluation: False - only evaluate success rate.
+                                 True - evaluate both success rate and accuracy
+                                 default is False.
+        """
+        rtt_supporting_aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
+            select_count=2)
+        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
+        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=stats)
+            asserts.assert_false(
+                stat['any_lci_mismatch'], "LCI mismatch", extras=stats)
+            asserts.assert_false(
+                stat['any_lcr_mismatch'], "LCR mismatch", extras=stats)
+            asserts.assert_false(
+                stat['invalid_num_attempted'],
+                "Invalid (0) number of attempts",
+                extras=stats)
+            asserts.assert_false(
+                stat['invalid_num_successful'],
+                "Invalid (0) number of successes",
+                extras=stats)
+            asserts.assert_equal(
+                stat['num_invalid_rssi'], 0, "Invalid RSSI", extras=stats)
+            asserts.assert_true(
+                stat['num_failures'] <=
+                self.rtt_max_failure_rate_two_sided_rtt_percentage *
+                stat['num_results'] / 100,
+                "Failure rate is too high",
+                extras=stats)
+            if accuracy_evaluation:
+                asserts.assert_true(
+                    stat['num_range_out_of_margin'] <=
+                    self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage *
+                    stat['num_success_results'] / 100,
+                    "Results exceeding error margin rate is too high",
+                    extras=stats)
+        asserts.explicit_pass("RTT test done", extras=stats)
+
+    @test_tracker_info(uuid="6705270f-924b-4bef-b50a-0f0a7eb9ce52")
+    def test_rtt_80211mc_supporting_aps(self):
+        """Scan for APs and perform RTT only to those which support 802.11mc,
+        Functionality test: Only evaluate success rate."""
+        dut = self.android_devices[0]
+        self.run_test_rtt_80211mc_supporting_aps(dut)
+
+    @test_tracker_info(uuid="56a8ca4c-b69d-436e-aa80-e86adb6f57d8")
+    def test_rtt_80211mc_supporting_aps_with_accuracy_evaluation(self):
+        """Scan for APs and perform RTT only to those which support 802.11mc,
+        Performance test: evaluate success rate and accuracy."""
+        dut = self.android_devices[0]
+        self.run_test_rtt_80211mc_supporting_aps(dut, accuracy_evaluation=True)
+
+    @test_tracker_info(uuid="eb3fc9f5-ae15-47f5-8468-697bb9aa9ddf")
+    def test_rtt_in_and_after_softap_mode(self):
+        """Verify behavior when a SoftAP is enabled and then disabled on the
+        device:
+
+        - SAP Enabled: depending on device characteristics RTT may succeed or
+                       fail.
+        - SAP Disabled: RTT must now succeed.
+        """
+        supp_required_params = ("dbs_supported_models", )
+        self.unpack_userparams(supp_required_params)
+
+        dut = self.android_devices[0]
+
+        rtt_supporting_aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
+            select_count=1)
+        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
+
+        # phase 1 (pre-SAP)
+        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats Phase 1 (pre-SAP)=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Phase 1 (pre-SAP) missing (timed-out) results",
+                extras=stats)
+
+        # phase 2 (SAP)
+        wutils.start_wifi_tethering(
+            dut,
+            self.SOFT_AP_SSID,
+            self.SOFT_AP_PASSWORD,
+            band=WIFI_CONFIG_APBAND_5G,
+            hidden=False)
+        time.sleep(self.WAIT_FOR_CONFIG_CHANGES_SEC)
+
+        if dut.model not in self.dbs_supported_models:
+            rutils.wait_for_event(dut, rconsts.BROADCAST_WIFI_RTT_NOT_AVAILABLE)
+            asserts.assert_false(dut.droid.wifiIsRttAvailable(),
+                                 "RTT is available")
+
+        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats Phase 2 (SAP)=%s", stats)
+
+        for bssid, stat in stats.items():
+            if dut.model in self.dbs_supported_models:
+                asserts.assert_true(
+                    stat['num_no_results'] == 0,
+                    "Phase 2 (SAP) missing (timed-out) results",
+                    extras=stats)
+            else:
+                asserts.assert_true(
+                    stat['num_success_results'] == 0,
+                    "Phase 2 (SAP) valid results - but unexpected in SAP!?",
+                    extras=stats)
+
+        # phase 3 (post-SAP)
+
+        # enabling Wi-Fi first: on some devices this will also disable SAP
+        # (that's the scenario we're primarily testing). Additionally,
+        # explicitly disable SAP (which may be a NOP on some devices).
+        wutils.wifi_toggle_state(dut, True)
+        time.sleep(self.WAIT_FOR_CONFIG_CHANGES_SEC)
+        wutils.stop_wifi_tethering(dut)
+
+        if dut.model not in self.dbs_supported_models:
+            rutils.wait_for_event(dut, rconsts.BROADCAST_WIFI_RTT_AVAILABLE)
+            asserts.assert_true(dut.droid.wifiIsRttAvailable(),
+                                "RTT is not available")
+
+        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
+                                    self.TIME_BETWEEN_ITERATIONS)
+        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
+                                       self.rtt_reference_distance_margin_mm,
+                                       self.rtt_min_expected_rssi_dbm,
+                                       self.lci_reference, self.lcr_reference)
+        dut.log.debug("Stats Phase 3 (post-SAP)=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Phase 3 (post-SAP) missing (timed-out) results",
+                extras=stats)
+
+    #########################################################################
+    #
+    # LEGACY API test code
+    #
+    #########################################################################
+
+    @test_tracker_info(uuid="18be9737-2f03-4e35-9a23-f722dea7b82d")
+    def test_legacy_rtt_80211mc_supporting_aps(self):
+        """Scan for APs and perform RTT only to those which support 802.11mc - using
+        the LEGACY API!
+        """
+        dut = self.android_devices[0]
+        rtt_supporting_aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
+            select_count=2)
+        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
+
+        rtt_configs = []
+        for ap in rtt_supporting_aps:
+            rtt_configs.append(self.rtt_config_from_scan_result(ap))
+        dut.log.debug("RTT configs=%s", rtt_configs)
+
+        results = []
+        num_missing = 0
+        num_failed_aborted = 0
+        for i in range(self.NUM_ITER):
+            idx = dut.droid.wifiRttStartRanging(rtt_configs)
+            event = None
+            try:
+                events = dut.ed.pop_events("WifiRttRanging%d" % idx, 30)
+                dut.log.debug("Event=%s", events)
+                for event in events:
+                    if rconsts.EVENT_CB_RANGING_KEY_RESULTS in event["data"]:
+                        results.append(event["data"][
+                            rconsts.EVENT_CB_RANGING_KEY_RESULTS])
+                    else:
+                        self.log.info("RTT failed/aborted - %s", event)
+                        results.append([])
+                        num_failed_aborted = num_failed_aborted + 1
+            except queue.Empty:
+                self.log.debug("Waiting for RTT event timed out.")
+                results.append([])
+                num_missing = num_missing + 1
+
+        # basic error checking:
+        # 1. no missing
+        # 2. no full failed/aborted (i.e. operation not even tried)
+        # 3. overall (all BSSIDs) success rate > threshold
+        asserts.assert_equal(
+            num_missing,
+            0,
+            "Missing results (timeout waiting for event)",
+            extras={"data": results})
+        asserts.assert_equal(
+            num_failed_aborted,
+            0,
+            "Failed or aborted operations (not tried)",
+            extras={"data": results})
+
+        num_results = 0
+        num_errors = 0
+        for result_group in results:
+            num_results = num_results + len(result_group)
+            for result in result_group:
+                if result["status"] != 0:
+                    num_errors = num_errors + 1
+
+        extras = [
+            results, {
+                "num_results": num_results,
+                "num_errors": num_errors
+            }
+        ]
+        asserts.assert_true(
+            num_errors <= self.rtt_max_failure_rate_two_sided_rtt_percentage *
+            num_results / 100,
+            "Failure rate is too high",
+            extras={"data": extras})
+        asserts.explicit_pass("RTT test done", extras={"data": extras})
+
+    def rtt_config_from_scan_result(self, scan_result):
+        """Creates an Rtt configuration based on the scan result of a network.
+        """
+        WifiEnums = wutils.WifiEnums
+        ScanResult = WifiEnums.ScanResult
+        RttParam = WifiEnums.RttParam
+        RttBW = WifiEnums.RttBW
+        RttPreamble = WifiEnums.RttPreamble
+        RttType = WifiEnums.RttType
+
+        scan_result_channel_width_to_rtt = {
+            ScanResult.CHANNEL_WIDTH_20MHZ: RttBW.BW_20_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_40MHZ: RttBW.BW_40_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_80MHZ: RttBW.BW_80_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_160MHZ: RttBW.BW_160_SUPPORT,
+            ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ: RttBW.BW_160_SUPPORT
+        }
+        p = {}
+        freq = scan_result[RttParam.frequency]
+        p[RttParam.frequency] = freq
+        p[RttParam.BSSID] = scan_result[WifiEnums.BSSID_KEY]
+        if freq > 5000:
+            p[RttParam.preamble] = RttPreamble.PREAMBLE_VHT
+        else:
+            p[RttParam.preamble] = RttPreamble.PREAMBLE_HT
+        cf0 = scan_result[RttParam.center_freq0]
+        if cf0 > 0:
+            p[RttParam.center_freq0] = cf0
+        cf1 = scan_result[RttParam.center_freq1]
+        if cf1 > 0:
+            p[RttParam.center_freq1] = cf1
+        cw = scan_result["channelWidth"]
+        p[RttParam.channel_width] = cw
+        p[RttParam.bandwidth] = scan_result_channel_width_to_rtt[cw]
+        if scan_result["is80211McRTTResponder"]:
+            p[RttParam.request_type] = RttType.TYPE_TWO_SIDED
+        else:
+            p[RttParam.request_type] = RttType.TYPE_ONE_SIDED
+        return p
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
new file mode 100644
index 0000000..a3262b1
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
@@ -0,0 +1,559 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class RangeAwareTest(AwareBaseTest, RttBaseTest):
+    """Test class for RTT ranging to Wi-Fi Aware peers"""
+    SERVICE_NAME = "GoogleTestServiceXY"
+
+    # Number of RTT iterations
+    NUM_ITER = 10
+
+    # Time gap (in seconds) between iterations
+    TIME_BETWEEN_ITERATIONS = 0
+
+    # Time gap (in seconds) when switching between Initiator and Responder
+    TIME_BETWEEN_ROLES = 4
+
+    # Min and max of the test subscribe range.
+    DISTANCE_MIN = 0
+    DISTANCE_MAX = 1000000
+
+    def setup_test(self):
+        """Manual setup here due to multiple inheritance: explicitly execute the
+        setup method from both parents.
+        """
+        AwareBaseTest.setup_test(self)
+        RttBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        """Manual teardown here due to multiple inheritance: explicitly execute the
+        teardown method from both parents.
+        """
+        AwareBaseTest.teardown_test(self)
+        RttBaseTest.teardown_test(self)
+
+    #############################################################################
+
+    def run_rtt_discovery(self, init_dut, resp_mac=None, resp_peer_id=None):
+        """Perform single RTT measurement, using Aware, from the Initiator DUT to
+        a Responder. The RTT Responder can be specified using its MAC address
+        (obtained using out- of-band discovery) or its Peer ID (using Aware
+        discovery).
+
+        Args:
+              init_dut: RTT Initiator device
+              resp_mac: MAC address of the RTT Responder device
+              resp_peer_id: Peer ID of the RTT Responder device
+        """
+        asserts.assert_true(
+            resp_mac is not None or resp_peer_id is not None,
+            "One of the Responder specifications (MAC or Peer ID)"
+            " must be provided!")
+        if resp_mac is not None:
+            id = init_dut.droid.wifiRttStartRangingToAwarePeerMac(resp_mac)
+        else:
+            id = init_dut.droid.wifiRttStartRangingToAwarePeerId(resp_peer_id)
+        event_name = rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                           id)
+        try:
+            event = init_dut.ed.pop_event(event_name, rutils.EVENT_TIMEOUT)
+            result = event["data"][rconsts.EVENT_CB_RANGING_KEY_RESULTS][0]
+            if resp_mac is not None:
+                rutils.validate_aware_mac_result(result, resp_mac, "DUT")
+            else:
+                rutils.validate_aware_peer_id_result(result, resp_peer_id,
+                                                     "DUT")
+            return result
+        except queue.Empty:
+            self.log.warning("Timed-out waiting for %s", event_name)
+            return None
+
+    def run_rtt_ib_discovery_set(self, do_both_directions, iter_count,
+                                 time_between_iterations, time_between_roles):
+        """Perform a set of RTT measurements, using in-band (Aware) discovery.
+
+        Args:
+              do_both_directions: False - perform all measurements in one direction,
+                                  True - perform 2 measurements one in both directions.
+              iter_count: Number of measurements to perform.
+              time_between_iterations: Number of seconds to wait between iterations.
+              time_between_roles: Number of seconds to wait when switching between
+                                  Initiator and Responder roles (only matters if
+                                  do_both_directions=True).
+
+        Returns: a list of the events containing the RTT results (or None for a
+        failed measurement). If both directions are tested then returns a list of
+        2 elements: one set for each direction.
+        """
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+         peer_id_on_pub) = autils.create_discovery_pair(
+             p_dut,
+             s_dut,
+             p_config=autils.add_ranging_to_pub(
+                 autils.create_discovery_config(
+                     self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+                 True),
+             s_config=autils.add_ranging_to_sub(
+                 autils.create_discovery_config(
+                     self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                 self.DISTANCE_MIN, self.DISTANCE_MAX),
+             device_startup_offset=self.device_startup_offset,
+             msg_id=self.get_next_msg_id())
+
+        resultsPS = []
+        resultsSP = []
+        for i in range(iter_count):
+            if i != 0 and time_between_iterations != 0:
+                time.sleep(time_between_iterations)
+
+            # perform RTT from pub -> sub
+            resultsPS.append(
+                self.run_rtt_discovery(p_dut, resp_peer_id=peer_id_on_pub))
+
+            if do_both_directions:
+                if time_between_roles != 0:
+                    time.sleep(time_between_roles)
+
+                # perform RTT from sub -> pub
+                resultsSP.append(
+                    self.run_rtt_discovery(s_dut, resp_peer_id=peer_id_on_sub))
+
+        return resultsPS if not do_both_directions else [resultsPS, resultsSP]
+
+    def run_rtt_oob_discovery_set(self, do_both_directions, iter_count,
+                                  time_between_iterations, time_between_roles):
+        """Perform a set of RTT measurements, using out-of-band discovery.
+
+        Args:
+              do_both_directions: False - perform all measurements in one direction,
+                                  True - perform 2 measurements one in both directions.
+              iter_count: Number of measurements to perform.
+              time_between_iterations: Number of seconds to wait between iterations.
+              time_between_roles: Number of seconds to wait when switching between
+                                  Initiator and Responder roles (only matters if
+                                  do_both_directions=True).
+              enable_ranging: True to enable Ranging, False to disable.
+
+        Returns: a list of the events containing the RTT results (or None for a
+        failed measurement). If both directions are tested then returns a list of
+        2 elements: one set for each direction.
+        """
+        dut0 = self.android_devices[0]
+        dut1 = self.android_devices[1]
+
+        id0, mac0 = autils.attach_with_identity(dut0)
+        id1, mac1 = autils.attach_with_identity(dut1)
+
+        # wait for for devices to synchronize with each other - there are no other
+        # mechanisms to make sure this happens for OOB discovery (except retrying
+        # to execute the data-path request)
+        time.sleep(autils.WAIT_FOR_CLUSTER)
+
+        # start publisher(s) on the Responder(s) with ranging enabled
+        p_config = autils.add_ranging_to_pub(
+            autils.create_discovery_config(self.SERVICE_NAME,
+                                           aconsts.PUBLISH_TYPE_UNSOLICITED),
+            enable_ranging=True)
+        dut1.droid.wifiAwarePublish(id1, p_config)
+        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+        if do_both_directions:
+            dut0.droid.wifiAwarePublish(id0, p_config)
+            autils.wait_for_event(dut0, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        results01 = []
+        results10 = []
+        for i in range(iter_count):
+            if i != 0 and time_between_iterations != 0:
+                time.sleep(time_between_iterations)
+
+            # perform RTT from dut0 -> dut1
+            results01.append(self.run_rtt_discovery(dut0, resp_mac=mac1))
+
+            if do_both_directions:
+                if time_between_roles != 0:
+                    time.sleep(time_between_roles)
+
+                # perform RTT from dut1 -> dut0
+                results10.append(self.run_rtt_discovery(dut1, resp_mac=mac0))
+
+        return results01 if not do_both_directions else [results01, results10]
+
+    def verify_results(self, results, results_reverse_direction=None, accuracy_evaluation=False):
+        """Verifies the results of the RTT experiment.
+
+        Args:
+              results: List of RTT results.
+              results_reverse_direction: List of RTT results executed in the
+                                        reverse direction. Optional.
+              accuracy_evaluation: False - only evaluate success rate.
+                                   True - evaluate both success rate and accuracy
+                                   default is False.
+        """
+        stats = rutils.extract_stats(results, self.rtt_reference_distance_mm,
+                                     self.rtt_reference_distance_margin_mm,
+                                     self.rtt_min_expected_rssi_dbm)
+        stats_reverse_direction = None
+        if results_reverse_direction is not None:
+            stats_reverse_direction = rutils.extract_stats(
+                results_reverse_direction, self.rtt_reference_distance_mm,
+                self.rtt_reference_distance_margin_mm,
+                self.rtt_min_expected_rssi_dbm)
+        self.log.debug("Stats: %s", stats)
+        if stats_reverse_direction is not None:
+            self.log.debug("Stats in reverse direction: %s",
+                           stats_reverse_direction)
+
+        extras = stats if stats_reverse_direction is None else {
+            "forward": stats,
+            "reverse": stats_reverse_direction
+        }
+
+        asserts.assert_true(
+            stats['num_no_results'] == 0,
+            "Missing (timed-out) results",
+            extras=extras)
+        asserts.assert_false(
+            stats['any_lci_mismatch'], "LCI mismatch", extras=extras)
+        asserts.assert_false(
+            stats['any_lcr_mismatch'], "LCR mismatch", extras=extras)
+        asserts.assert_false(
+            stats['invalid_num_attempted'],
+            "Invalid (0) number of attempts",
+            extras=stats)
+        asserts.assert_false(
+            stats['invalid_num_successful'],
+            "Invalid (0) number of successes",
+            extras=stats)
+        asserts.assert_equal(
+            stats['num_invalid_rssi'], 0, "Invalid RSSI", extras=extras)
+        asserts.assert_true(
+            stats['num_failures'] <=
+            self.rtt_max_failure_rate_two_sided_rtt_percentage *
+            stats['num_results'] / 100,
+            "Failure rate is too high",
+            extras=extras)
+        if accuracy_evaluation:
+            asserts.assert_true(
+                stats['num_range_out_of_margin'] <=
+                self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage *
+                stats['num_success_results'] / 100,
+                "Results exceeding error margin rate is too high",
+                extras=extras)
+
+        if stats_reverse_direction is not None:
+            asserts.assert_true(
+                stats_reverse_direction['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=extras)
+            asserts.assert_false(
+                stats_reverse_direction['any_lci_mismatch'], "LCI mismatch", extras=extras)
+            asserts.assert_false(
+                stats_reverse_direction['any_lcr_mismatch'], "LCR mismatch", extras=extras)
+            asserts.assert_equal(
+                stats_reverse_direction['num_invalid_rssi'], 0, "Invalid RSSI", extras=extras)
+            asserts.assert_true(
+                stats_reverse_direction['num_failures'] <=
+                self.rtt_max_failure_rate_two_sided_rtt_percentage *
+                stats_reverse_direction['num_results'] / 100,
+                "Failure rate is too high",
+                extras=extras)
+            if accuracy_evaluation:
+                asserts.assert_true(
+                    stats_reverse_direction['num_range_out_of_margin'] <=
+                    self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage *
+                    stats_reverse_direction['num_success_results'] / 100,
+                    "Results exceeding error margin rate is too high",
+                    extras=extras)
+
+        asserts.explicit_pass("RTT Aware test done", extras=extras)
+
+    def run_rtt_with_aware_session_disabled_ranging(self, disable_publish):
+        """Try to perform RTT operation when there publish or subscribe disabled ranging.
+
+        Args:
+            disable_publish: if true disable ranging on publish config, otherwise disable ranging on
+                            subscribe config
+        """
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+        pub_config = autils.create_discovery_config(
+            self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED)
+        sub_config = autils.create_discovery_config(
+            self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE)
+        if disable_publish:
+            sub_config = autils.add_ranging_to_sub(sub_config, self.DISTANCE_MIN, self.DISTANCE_MAX)
+        else:
+            pub_config = autils.add_ranging_to_pub(pub_config, True)
+        (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+         peer_id_on_pub) = autils.create_discovery_pair(
+            p_dut,
+            s_dut,
+            p_config=pub_config,
+            s_config=sub_config,
+            device_startup_offset=self.device_startup_offset,
+            msg_id=self.get_next_msg_id())
+        for i in range(self.NUM_ITER):
+            result_sub = self.run_rtt_discovery(s_dut, resp_peer_id=peer_id_on_sub)
+            asserts.assert_equal(rconsts.EVENT_CB_RANGING_STATUS_FAIL,
+                                 result_sub[rconsts.EVENT_CB_RANGING_KEY_STATUS],
+                                 "Ranging to publisher should fail.",
+                                 extras={"data": result_sub})
+
+        time.sleep(self.TIME_BETWEEN_ROLES)
+
+        for i in range(self.NUM_ITER):
+            result_pub = self.run_rtt_discovery(p_dut, resp_peer_id=peer_id_on_pub)
+            asserts.assert_equal(rconsts.EVENT_CB_RANGING_STATUS_FAIL,
+                                 result_pub[rconsts.EVENT_CB_RANGING_KEY_STATUS],
+                                 "Ranging to subscriber should fail.",
+                                 extras={"data": result_pub})
+
+    #############################################################################
+
+    @test_tracker_info(uuid="9e4e7ab4-2254-498c-9788-21e15ed9a370")
+    def test_rtt_oob_discovery_one_way(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
+        to communicate the MAC addresses to the peer. Test one-direction RTT only.
+        Functionality test: Only evaluate success rate.
+        """
+        rtt_results = self.run_rtt_oob_discovery_set(
+            do_both_directions=False,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results)
+
+    @test_tracker_info(uuid="22edba77-eeb2-43ee-875a-84437550ad84")
+    def test_rtt_oob_discovery_both_ways(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
+        to communicate the MAC addresses to the peer. Test RTT both-ways:
+        switching rapidly between Initiator and Responder.
+        Functionality test: Only evaluate success rate.
+        """
+        rtt_results1, rtt_results2 = self.run_rtt_oob_discovery_set(
+            do_both_directions=True,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results1, rtt_results2)
+
+    @test_tracker_info(uuid="18cef4be-95b4-4f7d-a140-5165874e7d1c")
+    def test_rtt_ib_discovery_one_way(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use in-band (Aware) discovery
+        to communicate the MAC addresses to the peer. Test one-direction RTT only.
+        Functionality test: Only evaluate success rate.
+        """
+        rtt_results = self.run_rtt_ib_discovery_set(
+            do_both_directions=False,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results)
+
+    @test_tracker_info(uuid="c67c8e70-c417-42d9-9bca-af3a89f1ddd9")
+    def test_rtt_ib_discovery_both_ways(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use in-band (Aware) discovery
+        to communicate the MAC addresses to the peer. Test RTT both-ways:
+        switching rapidly between Initiator and Responder.
+        Functionality test: Only evaluate success rate.
+        """
+        rtt_results1, rtt_results2 = self.run_rtt_ib_discovery_set(
+            do_both_directions=True,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results1, rtt_results2)
+
+    @test_tracker_info(uuid="3a1d19a2-7241-49e0-aaf2-0a1da4c29783")
+    def test_rtt_oob_discovery_one_way_with_accuracy_evaluation(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
+        to communicate the MAC addresses to the peer. Test one-direction RTT only.
+        Performance test: evaluate success rate and accuracy.
+        """
+        rtt_results = self.run_rtt_oob_discovery_set(
+            do_both_directions=False,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results, accuracy_evaluation=True)
+
+    @test_tracker_info(uuid="82f954a5-c0ec-4bc6-8940-3b72199328ac")
+    def test_rtt_oob_discovery_both_ways_with_accuracy_evaluation(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
+        to communicate the MAC addresses to the peer. Test RTT both-ways:
+        switching rapidly between Initiator and Responder.
+        Performance test: evaluate success rate and accuracy.
+        """
+        rtt_results1, rtt_results2 = self.run_rtt_oob_discovery_set(
+            do_both_directions=True,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results1, rtt_results2, accuracy_evaluation=True)
+
+    @test_tracker_info(uuid="4194e00c-ea49-488e-b67f-ad9360daa5fa")
+    def test_rtt_ib_discovery_one_way_with_accuracy_evaluation(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use in-band (Aware) discovery
+        to communicate the MAC addresses to the peer. Test one-direction RTT only.
+        Performance test: evaluate success rate and accuracy.
+        """
+        rtt_results = self.run_rtt_ib_discovery_set(
+            do_both_directions=False,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results, accuracy_evaluation=True)
+
+    @test_tracker_info(uuid="bea3ac8b-756d-4cd8-8936-b8bfe64676c9")
+    def test_rtt_ib_discovery_both_ways_with_accuracy_evaluation(self):
+        """Perform RTT between 2 Wi-Fi Aware devices. Use in-band (Aware) discovery
+        to communicate the MAC addresses to the peer. Test RTT both-ways:
+        switching rapidly between Initiator and Responder.
+        Performance test: evaluate success rate and accuracy.
+        """
+        rtt_results1, rtt_results2 = self.run_rtt_ib_discovery_set(
+            do_both_directions=True,
+            iter_count=self.NUM_ITER,
+            time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
+            time_between_roles=self.TIME_BETWEEN_ROLES)
+        self.verify_results(rtt_results1, rtt_results2, accuracy_evaluation=True)
+
+    @test_tracker_info(uuid="54f9693d-45e5-4979-adbb-1b875d217c0c")
+    def test_rtt_without_initiator_aware(self):
+        """Try to perform RTT operation when there is no local Aware session (on the
+        Initiator). The Responder is configured normally: Aware on and a Publisher
+        with Ranging enable. Should FAIL.
+        """
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        # Enable a Responder and start a Publisher
+        resp_id = resp_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        resp_ident_event = autils.wait_for_event(
+            resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        resp_mac = resp_ident_event['data']['mac']
+
+        resp_config = autils.add_ranging_to_pub(
+            autils.create_discovery_config(self.SERVICE_NAME,
+                                           aconsts.PUBLISH_TYPE_UNSOLICITED),
+            enable_ranging=True)
+        resp_dut.droid.wifiAwarePublish(resp_id, resp_config)
+        autils.wait_for_event(resp_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Initiate an RTT to Responder (no Aware started on Initiator!)
+        results = []
+        num_no_responses = 0
+        num_successes = 0
+        for i in range(self.NUM_ITER):
+            result = self.run_rtt_discovery(init_dut, resp_mac=resp_mac)
+            self.log.debug("result: %s", result)
+            results.append(result)
+            if result is None:
+                num_no_responses = num_no_responses + 1
+            elif (result[rconsts.EVENT_CB_RANGING_KEY_STATUS] ==
+                  rconsts.EVENT_CB_RANGING_STATUS_SUCCESS):
+                num_successes = num_successes + 1
+
+        asserts.assert_equal(
+            num_no_responses, 0, "No RTT response?", extras={"data": results})
+        asserts.assert_equal(
+            num_successes,
+            0,
+            "Aware RTT w/o Aware should FAIL!",
+            extras={"data": results})
+        asserts.explicit_pass("RTT Aware test done", extras={"data": results})
+
+    @test_tracker_info(uuid="87a69053-8261-4928-8ec1-c93aac7f3a8d")
+    def test_rtt_without_responder_aware(self):
+        """Try to perform RTT operation when there is no peer Aware session (on the
+        Responder). Should FAIL."""
+        init_dut = self.android_devices[0]
+        resp_dut = self.android_devices[1]
+
+        # Enable a Responder and start a Publisher
+        resp_id = resp_dut.droid.wifiAwareAttach(True)
+        autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+        resp_ident_event = autils.wait_for_event(
+            resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+        resp_mac = resp_ident_event['data']['mac']
+
+        resp_config = autils.add_ranging_to_pub(
+            autils.create_discovery_config(self.SERVICE_NAME,
+                                           aconsts.PUBLISH_TYPE_UNSOLICITED),
+            enable_ranging=True)
+        resp_dut.droid.wifiAwarePublish(resp_id, resp_config)
+        autils.wait_for_event(resp_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Disable Responder
+        resp_dut.droid.wifiAwareDestroy(resp_id)
+
+        # Enable the Initiator
+        init_id = init_dut.droid.wifiAwareAttach()
+        autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Initiate an RTT to Responder (no Aware started on Initiator!)
+        results = []
+        num_no_responses = 0
+        num_successes = 0
+        for i in range(self.NUM_ITER):
+            result = self.run_rtt_discovery(init_dut, resp_mac=resp_mac)
+            self.log.debug("result: %s", result)
+            results.append(result)
+            if result is None:
+                num_no_responses = num_no_responses + 1
+            elif (result[rconsts.EVENT_CB_RANGING_KEY_STATUS] ==
+                  rconsts.EVENT_CB_RANGING_STATUS_SUCCESS):
+                num_successes = num_successes + 1
+
+        asserts.assert_equal(
+            num_no_responses, 0, "No RTT response?", extras={"data": results})
+        asserts.assert_equal(
+            num_successes,
+            0,
+            "Aware RTT w/o Aware should FAIL!",
+            extras={"data": results})
+        asserts.explicit_pass("RTT Aware test done", extras={"data": results})
+
+    @test_tracker_info(uuid="80b0f96e-f87d-4dc9-a2b9-fae48558c8d8")
+    def test_rtt_with_publish_ranging_disabled(self):
+        """
+        Try to perform RTT operation when publish ranging disabled, should fail.
+        """
+        self.run_rtt_with_aware_session_disabled_ranging(True)
+
+    @test_tracker_info(uuid="cb93a902-9b7a-4734-ba81-864878f9fa55")
+    def test_rtt_with_subscribe_ranging_disabled(self):
+        """
+        Try to perform RTT operation when subscribe ranging disabled, should fail.
+        """
+        self.run_rtt_with_aware_session_disabled_ranging(False)
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
new file mode 100644
index 0000000..c23b5b0
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class RangeSoftApTest(RttBaseTest):
+    """Test class for RTT ranging to an Android Soft AP."""
+
+    # Soft AP SSID
+    SOFT_AP_SSID = "RTT_TEST_SSID"
+
+    # Soft AP Password (irrelevant)
+    SOFT_AP_PASSWORD = "ABCDEFGH"
+
+    # Number of RTT iterations
+    NUM_ITER = 10
+
+    #########################################################################
+
+    @test_tracker_info(uuid="578f0725-31e3-4e60-ad62-0212d93cf5b8")
+    def test_rtt_to_soft_ap(self):
+        """Set up a Soft AP on one device and try performing an RTT ranging to it
+    from another device. The attempt must fail - RTT on Soft AP must be
+    disabled."""
+        sap = self.android_devices[0]
+        sap.pretty_name = "SoftAP"
+        client = self.android_devices[1]
+        client.pretty_name = "Client"
+
+        # start Soft AP
+        wutils.start_wifi_tethering(
+            sap,
+            self.SOFT_AP_SSID,
+            self.SOFT_AP_PASSWORD,
+            band=WIFI_CONFIG_APBAND_5G,
+            hidden=False)
+
+        try:
+            # start scanning on the client
+            wutils.start_wifi_connection_scan_and_ensure_network_found(
+                client, self.SOFT_AP_SSID)
+            scans = client.droid.wifiGetScanResults()
+            scanned_softap = None
+            for scanned_ap in scans:
+                if scanned_ap[wutils.WifiEnums.SSID_KEY] == self.SOFT_AP_SSID:
+                    scanned_softap = scanned_ap
+                    break
+
+            asserts.assert_false(
+                scanned_softap == None,
+                "Soft AP not found in scan!",
+                extras=scans)
+
+            # validate that Soft AP does not advertise 802.11mc support
+            asserts.assert_false(
+                rconsts.SCAN_RESULT_KEY_RTT_RESPONDER in scanned_softap
+                and scanned_softap[rconsts.SCAN_RESULT_KEY_RTT_RESPONDER],
+                "Soft AP advertises itself as supporting 802.11mc!",
+                extras=scanned_softap)
+
+            # falsify the SoftAP's support for IEEE 802.11 so we try a 2-sided RTT
+            scanned_softap[
+                rconsts.SCAN_RESULT_KEY_RTT_RESPONDER] = True  # falsify
+
+            # actually try ranging to the Soft AP
+            events = rutils.run_ranging(client, [scanned_softap],
+                                        self.NUM_ITER, 0)
+            stats = rutils.analyze_results(
+                events, self.rtt_reference_distance_mm,
+                self.rtt_reference_distance_margin_mm,
+                self.rtt_min_expected_rssi_dbm, self.lci_reference,
+                self.lcr_reference)
+
+            asserts.assert_equal(
+                stats[scanned_ap[wutils.WifiEnums.BSSID_KEY]]['num_failures'],
+                self.NUM_ITER,
+                "Some RTT operations to Soft AP succeed!?",
+                extras=stats)
+
+            asserts.explicit_pass(
+                "SoftAP + RTT validation done", extras=events)
+        finally:
+            wutils.stop_wifi_tethering(sap)
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py b/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
new file mode 100644
index 0000000..703efac
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
@@ -0,0 +1,118 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts import utils
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class RttDisableTest(WifiBaseTest, RttBaseTest):
+    """Test class for RTT ranging enable/disable flows."""
+
+    MODE_DISABLE_WIFI = 0
+    MODE_ENABLE_DOZE = 1
+    MODE_DISABLE_LOCATIONING = 2
+
+    def setup_test(self):
+        RttBaseTest.setup_test(self)
+
+    def setup_class(self):
+        super().setup_class()
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True)
+
+    def run_disable_rtt(self, disable_mode):
+        """Validate the RTT disabled flows: whether by disabling Wi-Fi or entering
+    doze mode.
+
+    Args:
+      disable_mode: The particular mechanism in which RTT is disabled. One of
+                    the MODE_* constants.
+    """
+        dut = self.android_devices[0]
+
+        # validate start-up conditions
+        asserts.assert_true(dut.droid.wifiIsRttAvailable(),
+                            "RTT is not available")
+
+        # scan to get some APs to be used later
+        all_aps = rutils.select_best_scan_results(
+            rutils.scan_networks(dut), select_count=1)
+        asserts.assert_true(len(all_aps) > 0, "Need at least one visible AP!")
+
+        # disable RTT and validate broadcast & API
+        if disable_mode == self.MODE_DISABLE_WIFI:
+            # disabling Wi-Fi is not sufficient: since scan mode (and hence RTT) will
+            # remain enabled - we need to disable the Wi-Fi chip aka Airplane Mode
+            asserts.assert_true(
+                utils.force_airplane_mode(dut, True),
+                "Can not turn on airplane mode on: %s" % dut.serial)
+        elif disable_mode == self.MODE_ENABLE_DOZE:
+            asserts.assert_true(utils.enable_doze(dut), "Can't enable doze")
+        elif disable_mode == self.MODE_DISABLE_LOCATIONING:
+            utils.set_location_service(dut, False)
+
+        rutils.wait_for_event(dut, rconsts.BROADCAST_WIFI_RTT_NOT_AVAILABLE)
+        asserts.assert_false(dut.droid.wifiIsRttAvailable(),
+                             "RTT is available")
+
+        # request a range and validate error
+        id = dut.droid.wifiRttStartRangingToAccessPoints(all_aps[0:1])
+        event = rutils.wait_for_event(
+            dut, rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_FAIL, id))
+        asserts.assert_equal(
+            event["data"][rconsts.EVENT_CB_RANGING_KEY_STATUS],
+            rconsts.RANGING_FAIL_CODE_RTT_NOT_AVAILABLE, "Invalid error code")
+
+        # enable RTT and validate broadcast & API
+        if disable_mode == self.MODE_DISABLE_WIFI:
+            asserts.assert_true(
+                utils.force_airplane_mode(dut, False),
+                "Can not turn off airplane mode on: %s" % dut.serial)
+        elif disable_mode == self.MODE_ENABLE_DOZE:
+            asserts.assert_true(utils.disable_doze(dut), "Can't disable doze")
+        elif disable_mode == self.MODE_DISABLE_LOCATIONING:
+            utils.set_location_service(dut, True)
+
+        rutils.wait_for_event(dut, rconsts.BROADCAST_WIFI_RTT_AVAILABLE)
+        asserts.assert_true(dut.droid.wifiIsRttAvailable(),
+                            "RTT is not available")
+
+    ############################################################################
+
+    @test_tracker_info(uuid="498c49ab-a188-4612-998d-c47b35ff285e")
+    def test_disable_wifi(self):
+        """Validate that getting expected broadcast when Wi-Fi is disabled and that
+    any range requests are rejected."""
+        self.run_disable_rtt(self.MODE_DISABLE_WIFI)
+
+    @test_tracker_info(uuid="f71f731f-4aaf-402b-8595-db94b625b544")
+    def test_enable_doze(self):
+        """Validate that getting expected broadcast when RTT is disabled due to doze
+    mode and that any range requests are rejected."""
+        self.run_disable_rtt(self.MODE_ENABLE_DOZE)
+
+    @test_tracker_info(uuid="6a1c83a8-9eaf-49db-b547-5131cba0eafe")
+    def test_disable_location(self):
+        """Validate that getting expected broadcast when locationing is disabled and
+    that any range requests are rejected."""
+        self.run_disable_rtt(self.MODE_DISABLE_LOCATIONING)
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py b/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py
new file mode 100644
index 0000000..c91521d
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py
@@ -0,0 +1,146 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import random
+import time
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class RttRequestManagementTest(RttBaseTest):
+    """Test class for RTT request management flows."""
+
+    SPAMMING_LIMIT = 20
+
+    #############################################################################
+
+    @test_tracker_info(uuid="29ff4a02-2952-47df-bf56-64f30c963093")
+    def test_cancel_ranging(self):
+        """Request a 'large' number of range operations with various UIDs (using the
+    work-source API), then cancel some of them.
+
+    We can't guarantee a reaction time - it is possible that a cancelled test
+    was already finished and it's results dispatched back. The test therefore
+    stacks the request queue. The sequence is:
+
+    - Request:
+      - 50 tests @ UIDs = {uid1, uid2, uid3}
+      - 2 tests @ UIDs = {uid2, uid3}
+      - 1 test2 @ UIDs = {uid1, uid2, uid3}
+    - Cancel UIDs = {uid2, uid3}
+
+    Expect to receive only 51 results.
+    """
+        dut = self.android_devices[0]
+        max_peers = dut.droid.wifiRttMaxPeersInRequest()
+
+        all_uids = [1000, 20,
+                    30]  # 1000 = System Server (makes requests foreground)
+        some_uids = [20, 30]
+
+        aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
+            select_count=1)
+        dut.log.info("RTT Supporting APs=%s", aps)
+
+        asserts.assert_true(
+            len(aps) > 0, "Need at least one AP which supports 802.11mc!")
+        if len(aps) > max_peers:
+            aps = aps[0:max_peers]
+
+        group1_ids = []
+        group2_ids = []
+        group3_ids = []
+
+        # step 1: request <spam_limit> ranging operations on [uid1, uid2, uid3]
+        for i in range(self.SPAMMING_LIMIT):
+            group1_ids.append(
+                dut.droid.wifiRttStartRangingToAccessPoints(aps, all_uids))
+
+        # step 2: request 2 ranging operations on [uid2, uid3]
+        for i in range(2):
+            group2_ids.append(
+                dut.droid.wifiRttStartRangingToAccessPoints(aps, some_uids))
+
+        # step 3: request 1 ranging operation on [uid1, uid2, uid3]
+        for i in range(1):
+            group3_ids.append(
+                dut.droid.wifiRttStartRangingToAccessPoints(aps, all_uids))
+
+        # step 4: cancel ranging requests on [uid2, uid3]
+        dut.droid.wifiRttCancelRanging(some_uids)
+
+        # collect results
+        for i in range(len(group1_ids)):
+            rutils.wait_for_event(
+                dut,
+                rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                      group1_ids[i]))
+        time.sleep(
+            rutils.EVENT_TIMEOUT)  # optimize time-outs below to single one
+        for i in range(len(group2_ids)):
+            rutils.fail_on_event(
+                dut,
+                rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                      group2_ids[i]), 0)
+        for i in range(len(group3_ids)):
+            rutils.wait_for_event(
+                dut,
+                rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                      group3_ids[i]))
+
+    @test_tracker_info(uuid="48297480-c026-4780-8c13-476e7bea440c")
+    def test_throttling(self):
+        """Request sequential range operations using a bogus UID (which will
+    translate as a throttled process) and similarly using the ACTS/sl4a as
+    the source (a foreground/unthrottled process)."""
+        dut = self.android_devices[0]
+        max_peers = dut.droid.wifiRttMaxPeersInRequest()
+
+        # Need to use a random number since the system keeps states and so the
+        # background uid will be throttled on the next run of this script
+        fake_uid = [random.randint(10, 9999)]
+
+        aps = rutils.select_best_scan_results(
+            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
+            select_count=1)
+        dut.log.info("RTT Supporting APs=%s", aps)
+
+        asserts.assert_true(
+            len(aps) > 0, "Need at least one AP which supports 802.11mc!")
+        if len(aps) > max_peers:
+            aps = aps[0:max_peers]
+
+        id1 = dut.droid.wifiRttStartRangingToAccessPoints(aps)  # as ACTS/sl4a
+        id2 = dut.droid.wifiRttStartRangingToAccessPoints(aps, fake_uid)
+        id3 = dut.droid.wifiRttStartRangingToAccessPoints(aps, fake_uid)
+        id4 = dut.droid.wifiRttStartRangingToAccessPoints(aps)  # as ACTS/sl4a
+
+        rutils.wait_for_event(
+            dut, rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                       id1))
+        rutils.wait_for_event(
+            dut, rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                       id2))
+        rutils.wait_for_event(
+            dut, rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_FAIL, id3))
+        rutils.wait_for_event(
+            dut, rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT,
+                                       id4))
diff --git a/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py b/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py
new file mode 100644
index 0000000..d0e9fe9
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py
@@ -0,0 +1,83 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class StressRangeApTest(RttBaseTest):
+    """Test class for stress testing of RTT ranging to Access Points"""
+
+    #############################################################################
+
+    def test_rtt_supporting_ap_only(self):
+        """Scan for APs and perform RTT only to those which support 802.11mc.
+
+    Stress test: repeat ranging to the same AP. Verify rate of success and
+    stability of results.
+    """
+        dut = self.android_devices[0]
+        rtt_supporting_aps = rutils.scan_with_rtt_support_constraint(
+            dut, True, repeat=10)
+        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
+
+        num_iter = self.stress_test_min_iteration_count
+
+        max_peers = dut.droid.wifiRttMaxPeersInRequest()
+        asserts.assert_true(
+            len(rtt_supporting_aps) > 0,
+            "Need at least one AP which supports 802.11mc!")
+        if len(rtt_supporting_aps) > max_peers:
+            rtt_supporting_aps = rtt_supporting_aps[0:max_peers]
+
+        events = rutils.run_ranging(dut, rtt_supporting_aps, num_iter, 0,
+                                    self.stress_test_target_run_time_sec)
+        stats = rutils.analyze_results(
+            events,
+            self.rtt_reference_distance_mm,
+            self.rtt_reference_distance_margin_mm,
+            self.rtt_min_expected_rssi_dbm,
+            self.lci_reference,
+            self.lcr_reference,
+            summary_only=True)
+        dut.log.debug("Stats=%s", stats)
+
+        for bssid, stat in stats.items():
+            asserts.assert_true(
+                stat['num_no_results'] == 0,
+                "Missing (timed-out) results",
+                extras=stats)
+            asserts.assert_false(
+                stat['any_lci_mismatch'], "LCI mismatch", extras=stats)
+            asserts.assert_false(
+                stat['any_lcr_mismatch'], "LCR mismatch", extras=stats)
+            asserts.assert_equal(
+                stat['num_invalid_rssi'], 0, "Invalid RSSI", extras=stats)
+            asserts.assert_true(
+                stat['num_failures'] <=
+                self.rtt_max_failure_rate_two_sided_rtt_percentage *
+                stat['num_results'] / 100,
+                "Failure rate is too high",
+                extras=stats)
+            asserts.assert_true(
+                stat['num_range_out_of_margin'] <=
+                self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage *
+                stat['num_success_results'] / 100,
+                "Results exceeding error margin rate is too high",
+                extras=stats)
+        asserts.explicit_pass("RTT test done", extras=stats)
diff --git a/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py b/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py
new file mode 100644
index 0000000..ccf8b9d
--- /dev/null
+++ b/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.test_utils.wifi.rtt import rtt_const as rconsts
+from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+
+
+class StressRangeAwareTest(AwareBaseTest, RttBaseTest):
+    """Test class for stress testing of RTT ranging to Wi-Fi Aware peers."""
+    SERVICE_NAME = "GoogleTestServiceXY"
+
+    def setup_test(self):
+        """Manual setup here due to multiple inheritance: explicitly execute the
+    setup method from both parents."""
+        AwareBaseTest.setup_test(self)
+        RttBaseTest.setup_test(self)
+
+    def teardown_test(self):
+        """Manual teardown here due to multiple inheritance: explicitly execute the
+    teardown method from both parents."""
+        AwareBaseTest.teardown_test(self)
+        RttBaseTest.teardown_test(self)
+
+    #############################################################################
+
+    def run_rtt_discovery(self, init_dut, resp_mac=None, resp_peer_id=None):
+        """Perform single RTT measurement, using Aware, from the Initiator DUT to
+    a Responder. The RTT Responder can be specified using its MAC address
+    (obtained using out- of-band discovery) or its Peer ID (using Aware
+    discovery).
+
+    Args:
+      init_dut: RTT Initiator device
+      resp_mac: MAC address of the RTT Responder device
+      resp_peer_id: Peer ID of the RTT Responder device
+    """
+        asserts.assert_true(
+            resp_mac is not None or resp_peer_id is not None,
+            "One of the Responder specifications (MAC or Peer ID)"
+            " must be provided!")
+        if resp_mac is not None:
+            id = init_dut.droid.wifiRttStartRangingToAwarePeerMac(resp_mac)
+        else:
+            id = init_dut.droid.wifiRttStartRangingToAwarePeerId(resp_peer_id)
+        try:
+            event = init_dut.ed.pop_event(
+                rutils.decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT, id),
+                rutils.EVENT_TIMEOUT)
+            result = event["data"][rconsts.EVENT_CB_RANGING_KEY_RESULTS][0]
+            if resp_mac is not None:
+                rutils.validate_aware_mac_result(result, resp_mac, "DUT")
+            else:
+                rutils.validate_aware_peer_id_result(result, resp_peer_id,
+                                                     "DUT")
+            return result
+        except queue.Empty:
+            return None
+
+    def test_stress_rtt_ib_discovery_set(self):
+        """Perform a set of RTT measurements, using in-band (Aware) discovery, and
+    switching Initiator and Responder roles repeatedly.
+
+    Stress test: repeat ranging operations. Verify rate of success and
+    stability of results.
+    """
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+         peer_id_on_pub) = autils.create_discovery_pair(
+             p_dut,
+             s_dut,
+             p_config=autils.add_ranging_to_pub(
+                 autils.create_discovery_config(
+                     self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+                 True),
+             s_config=autils.add_ranging_to_pub(
+                 autils.create_discovery_config(
+                     self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE), True),
+             device_startup_offset=self.device_startup_offset,
+             msg_id=self.get_next_msg_id())
+
+        results = []
+        start_clock = time.time()
+        iterations_done = 0
+        run_time = 0
+        while iterations_done < self.stress_test_min_iteration_count or (
+                self.stress_test_target_run_time_sec != 0
+                and run_time < self.stress_test_target_run_time_sec):
+            results.append(
+                self.run_rtt_discovery(p_dut, resp_peer_id=peer_id_on_pub))
+            results.append(
+                self.run_rtt_discovery(s_dut, resp_peer_id=peer_id_on_sub))
+
+            iterations_done = iterations_done + 1
+            run_time = time.time() - start_clock
+
+        stats = rutils.extract_stats(
+            results,
+            self.rtt_reference_distance_mm,
+            self.rtt_reference_distance_margin_mm,
+            self.rtt_min_expected_rssi_dbm,
+            summary_only=True)
+        self.log.debug("Stats: %s", stats)
+        asserts.assert_true(
+            stats['num_no_results'] == 0,
+            "Missing (timed-out) results",
+            extras=stats)
+        asserts.assert_false(
+            stats['any_lci_mismatch'], "LCI mismatch", extras=stats)
+        asserts.assert_false(
+            stats['any_lcr_mismatch'], "LCR mismatch", extras=stats)
+        asserts.assert_equal(
+            stats['num_invalid_rssi'], 0, "Invalid RSSI", extras=stats)
+        asserts.assert_true(
+            stats['num_failures'] <=
+            self.rtt_max_failure_rate_two_sided_rtt_percentage *
+            stats['num_results'] / 100,
+            "Failure rate is too high",
+            extras=stats)
+        asserts.assert_true(
+            stats['num_range_out_of_margin'] <=
+            self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage *
+            stats['num_success_results'] / 100,
+            "Results exceeding error margin rate is too high",
+            extras=stats)