Merge "Add Fuchsia DHCP Duplicate Address Test"
diff --git a/acts/framework/acts/controllers/OWNERS b/acts/framework/acts/controllers/OWNERS
index 03943d0..64922ab 100644
--- a/acts/framework/acts/controllers/OWNERS
+++ b/acts/framework/acts/controllers/OWNERS
@@ -2,4 +2,4 @@
 per-file fuchsia_device.py = chcl@google.com, dhobsd@google.com, haydennix@google.com, jmbrenna@google.com, mnck@google.com, silberst@google.com, tturney@google.com
 per-file bluetooth_pts_device.py = tturney@google.com
 per-file cellular_simulator.py = iguarna@google.com, chaoyangf@google.com, codycaldwell@google.com, yixiang@google.com
-per-file openwrt_ap.py = jerrypcchen@google.com, martschneider@google.com, gmoturu@google.com
+per-file openwrt_ap.py = jerrypcchen@google.com, martschneider@google.com, gmoturu@google.com, sishichen@google.com
diff --git a/acts/framework/acts/controllers/__init__.py b/acts/framework/acts/controllers/__init__.py
index b805e9e..56b4817 100644
--- a/acts/framework/acts/controllers/__init__.py
+++ b/acts/framework/acts/controllers/__init__.py
@@ -26,5 +26,6 @@
 __all__ = [
     "android_device", "attenuator", "bluetooth_pts_device", "monsoon",
     "access_point", "iperf_server", "packet_sender", "arduino_wifi_dongle",
-    "packet_capture", "fuchsia_device", "pdu", "openwrt_ap"
+    "packet_capture", "fuchsia_device", "pdu", "openwrt_ap", "tigertail",
+    "asus_axe11000_ap"
 ]
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd.py b/acts/framework/acts/controllers/ap_lib/hostapd.py
index efaee78..d08caf2 100644
--- a/acts/framework/acts/controllers/ap_lib/hostapd.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd.py
@@ -199,12 +199,14 @@
         """
         start_time = time.time()
         while time.time() - start_time < timeout:
+            time.sleep(0.1)
             success = self._shell.search_file('Setup of interface done',
                                               self._log_file)
             if success:
                 return
+            self._scan_for_errors(False)
 
-            self._scan_for_errors(True)
+        self._scan_for_errors(True)
 
     def _scan_for_errors(self, should_be_up):
         """Scans the hostapd log for any errors.
diff --git a/acts/framework/acts/controllers/ap_lib/radvd.py b/acts/framework/acts/controllers/ap_lib/radvd.py
index 187f9e7..e88701e 100644
--- a/acts/framework/acts/controllers/ap_lib/radvd.py
+++ b/acts/framework/acts/controllers/ap_lib/radvd.py
@@ -134,8 +134,9 @@
         """
         start_time = time.time()
         while time.time() - start_time < timeout and not self.is_alive():
-            self._scan_for_errors(True)
             time.sleep(0.1)
+            self._scan_for_errors(False)
+        self._scan_for_errors(True)
 
     def _scan_for_errors(self, should_be_up):
         """Scans the radvd log for any errors.
diff --git a/acts/framework/acts/controllers/asus_axe11000_ap.py b/acts/framework/acts/controllers/asus_axe11000_ap.py
new file mode 100644
index 0000000..d19af3b
--- /dev/null
+++ b/acts/framework/acts/controllers/asus_axe11000_ap.py
@@ -0,0 +1,763 @@
+"""Controller for Asus AXE11000 access point."""
+
+import time
+from acts import logger
+from selenium import webdriver
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.support.ui import Select
+
+MOBLY_CONTROLLER_CONFIG_NAME = "AsusAXE11000AP"
+ACTS_CONTROLLER_REFERENCE_NAME = "access_points"
+
+# Access point UI parameters
+USERNAME = "login_username"
+PASSWORD = "login_passwd"
+SIGN_IN_ID = "button"
+APPLY_BUTTON = "apply_btn"
+APPLY_BUTTON_ID = "applyButton"
+WIRELESS_SETTINGS = "Advanced_Wireless_Content_menu"
+GENERAL_TAB = "Advanced_Wireless_Content_tab"
+PROFESSIONAL_TAB = "Advanced_WAdvanced_Content_tab"
+HE_MODE_ID = "he_mode_field"
+WL_UNIT = "wl_unit"
+WL_11AX = "wl_11ax"
+WL_RADIO = "wl_radio"
+WL_CLOSED = "wl_closed"
+RADIO = "radio"
+BAND_2G_CHANNEL = "band0_channel"
+BAND_5G_CHANNEL = "band1_channel"
+BAND_6G_CHANNEL = "band2_channel"
+BAND_2G_AUTH = "band0_auth_mode_x"
+BAND_5G_AUTH = "band1_auth_mode_x"
+BAND_6G_AUTH = "band2_auth_mode_x"
+BAND_2G_SSID = "band0_ssid"
+BAND_5G_SSID = "band1_ssid"
+BAND_6G_SSID = "band2_ssid"
+BAND_2G_PSK = "band0_wpa_psk"
+BAND_5G_PSK = "band1_wpa_psk"
+BAND_6G_PSK = "band2_wpa_psk"
+BAND_2G_RAD_IP = "band0_radius_ipaddr"
+BAND_5G_RAD_IP = "band1_radius_ipaddr"
+BAND_2G_RAD_PORT = "band0_radius_port"
+BAND_5G_RAD_PORT = "band1_radius_port"
+BAND_2G_RAD_KEY = "band0_radius_key"
+BAND_5G_RAD_KEY = "band1_radius_key"
+SMART_CONNECT = "smartcon_enable_field"
+BROWSER_WAIT_SHORT_TIMEOUT = 6
+BROWSER_WAIT_TIMEOUT = 15
+BROWSER_WAIT_LONG_TIMEOUT = 90
+BROWSER_WAIT_VERY_LONG_TIMEOUT = 180
+
+# Access point supported modes, channels
+VALID_BANDS = ["2g", "5g", "6g"]
+WL_BAND_VALUE = {"2g": "0", "5g": "1", "6g": "2"}
+CHANNELS_2G = {
+    0: "0",
+    1: "1",
+    2: "2",
+    3: "3",
+    4: "4",
+    5: "5",
+    6: "6",
+    7: "7",
+    8: "8",
+    9: "9",
+    10: "10",
+    11: "11"
+}
+CHANNELS_5G = {
+    0: "0",
+    36: "36/160",
+    40: "40/160",
+    44: "44/160",
+    48: "48/160",
+    52: "52/160",
+    56: "56/160",
+    60: "60/160",
+    64: "64/160",
+    100: "100/160",
+    104: "104/160",
+    108: "108/160",
+    112: "112/160",
+    116: "116/160",
+    120: "120/160",
+    124: "124/160",
+    128: "128/160",
+    132: "132/80",
+    136: "136/80",
+    140: "140/80",
+    144: "144/80",
+    149: "149/80",
+    153: "153/80",
+    157: "157/80",
+    161: "161/80",
+    165: "165"
+}
+CHANNELS_6G = {
+    0: "0",
+    37: "6g37/160",
+    53: "6g53/160",
+    69: "6g69/160",
+    85: "6g85/160",
+    101: "6g101/160",
+    117: "6g117/160",
+    133: "6g133/160",
+    149: "6g149/160",
+    165: "6g165/160",
+    181: "6g181/160",
+    197: "6g197/160",
+    213: "6g213/160"
+}
+
+
+def create(configs):
+  """Creates ap controllers from a json config."""
+  return [AsusAXE11000AP(c) for c in configs]
+
+
+def destroy(aps):
+  """Destroys a list of ap controllers."""
+  for ap in aps:
+    ap.reset_to_default_ap_settings()
+    ap.driver.quit()
+
+
+class AsusAXE11000AP(object):
+  """Asus AXE11000 AccessPoint controller.
+
+  Controller class for Asus AXE11000 6GHz AP. This class provides methods to
+  configure the AP with different settings required for 11ax and 6GHz testing.
+  The controller uses chrome webdriver to communicate with the AP.
+
+  The controller object is initiated in the test class. The ACTS test runner
+  calls this controller using the 'AsusAXE11000AP' keyword in the ACTS config
+  file. The AP is reset to default settings and this is handled during the
+  test teardown.
+
+  Attributes:
+    ip: IP address to reach the AP.
+    port: Port numnber to reach the AP.
+    protocol: Protcol to reach the AP (http/https).
+    username: Username to login to the AP.
+    password: Password to login to the AP.
+    config_page: web url to login to the AP.
+    ap_settings: AP settings configured at any given point.
+    default_ap_settings: Default AP settings before running the tests.
+    driver: chrome webdriver object to update the settings.
+  """
+
+  def __init__(self, config):
+    """Initialize AP.
+
+    Creates a chrome webdriver object based on the router parameters.
+    The webdriver will login to the router and goes to the wireless settings
+    page. This object will be used to change the router settings required for
+    the test cases. Required parameters are <ip_address>, <port>, <protocol>,
+    <admin_username> and <admin_password>.
+
+    Url: <procotol>://<ip_address>:<port>/Main_Login.asp
+    Login: <admin_username>/<admin_password>
+
+    Args:
+      config: dict, dictionary of router parameters required for webdriver.
+    """
+    self.ip = config["ip_address"]
+    self.port = config["port"]
+    self.protocol = config["protocol"]
+    self.username = config["admin_username"]
+    self.password = config["admin_password"]
+    lambda_msg = lambda msg: "[AsusAXE11000AP|%s] %s" % (self.ip, msg)
+    self.log = logger.create_logger(lambda_msg)
+    self.ap_settings = {"2g": {}, "5g": {}, "6g": {},}
+    self.config_page = (
+        "{protocol}://{ip_address}:{port}/Main_Login.asp").format(
+            protocol=self.protocol, ip_address=self.ip, port=self.port)
+    self.chrome_options = Options()
+    self.chrome_options.add_argument("--headless")
+    self.chrome_options.add_argument("--no-sandbox")
+    self.driver = webdriver.Chrome(options=self.chrome_options)
+    self.driver.implicitly_wait(BROWSER_WAIT_TIMEOUT*2)
+    self.driver.get(self.config_page)
+    self.driver.find_element_by_name(USERNAME).send_keys(self.username)
+    self.driver.find_element_by_name(PASSWORD).send_keys(self.password)
+    self.driver.find_element_by_id(SIGN_IN_ID).click()
+    self._wait_for_web_element(self.driver.find_element_by_id,
+                               WIRELESS_SETTINGS)
+    self.driver.find_element_by_id(WIRELESS_SETTINGS).click()
+    self._wait_for_web_element(self.driver.find_element_by_id, SMART_CONNECT)
+    self._update_ap_settings()
+    self.default_ap_settings = self.ap_settings.copy()
+
+  ### Helper methods ###
+
+  def _wait_for_web_element(self,
+                            find_element,
+                            element,
+                            attribute=None,
+                            value=None):
+    """Verifies click actions/selections work.
+
+    Args:
+      find_element: func(), webdriver method to call
+      element: str, web element to look for. Ex: id, class, name
+      attribute: str, attribute to get from a webelement
+      value: str, verify attribute is set to the correct value
+
+    Raises:
+      ValueError: An error occurred if expected attribute not found.
+    """
+    curr_time = time.time()
+    while time.time() < curr_time + BROWSER_WAIT_TIMEOUT*4:
+      time.sleep(2)
+      try:
+        x = find_element(element)
+        if attribute and str(value) not in x.get_attribute(attribute):
+          raise ValueError("Attribute is not set to the right value")
+        return
+      except NoSuchElementException:
+        pass
+    raise ValueError("Failed to find web element: %s" % element)
+
+  def _update_ap_settings_2g_band(self):
+    """Read settings configured on 2g band.
+
+    Parameters Updated:
+      security type: open, wpa2-psk, wpa3-sae or wpa2-ent.
+      ssid: SSID of the wifi network.
+      password: password of the wifi network (if psk or sae network).
+      radius server ip: Radius server IP addr (if ent network).
+      radius server port: Radius server Port number (if ent network).
+      radius server secret: Radius server secret (if ent network).
+      channel: 2G band channel.
+    """
+    dict_2g = {}
+    dict_2g["security"] = self.driver.find_element_by_name(
+        BAND_2G_AUTH).get_attribute("value")
+    dict_2g["SSID"] = self.driver.find_element_by_name(
+        BAND_2G_SSID).get_attribute("value")
+    if dict_2g["security"] == "psk2" or dict_2g["security"] == "sae":
+      dict_2g["password"] = self.driver.find_element_by_name(
+          BAND_2G_PSK).get_attribute("value")
+    elif dict_2g["security"] == "wpa2":
+      dict_2g["radius_ip_addr"] = self.driver.find_element_by_name(
+          BAND_2G_RAD_IP).get_attribute("value")
+      dict_2g["radius_port"] = self.driver.find_element_by_name(
+          BAND_2G_RAD_PORT).get_attribute("value")
+      dict_2g["radius_secret"] = self.driver.find_element_by_name(
+          BAND_2G_RAD_KEY).get_attribute("value")
+    channel_field = self._get_webdriver_elements_for_channels(band)
+    ch_val = self.driver.find_element_by_name(channel_field).get_attribute(
+        "value")
+    channel = 0
+    for key, val in CHANNELS_2G.items():
+      if val == ch_val:
+        channel = key
+        break
+    self.ap_settings["2g"] = dict_2g.copy()
+    self.ap_settings["2g"]["channel"] = channel
+
+  def _update_ap_settings_5g_band(self):
+    """Read settings configured on 5g band.
+
+    Parameters Updated:
+      security type: open, wpa2-psk, wpa3-sae or wpa2-ent.
+      ssid: SSID of the wifi network.
+      password: password of the wifi network (if psk or sae network).
+      radius server ip: Radius server IP addr (if ent network).
+      radius server port: Radius server Port number (if ent network).
+      radius server secret: Radius server secret (if ent network).
+      channel: 5G band channel.
+    """
+    dict_5g = {}
+    dict_5g["security"] = self.driver.find_element_by_name(
+        BAND_5G_AUTH).get_attribute("value")
+    dict_5g["SSID"] = self.driver.find_element_by_name(
+        BAND_5G_SSID).get_attribute("value")
+    if dict_5g["security"] == "psk2" or dict_5g["security"] == "sae":
+      dict_5g["password"] = self.driver.find_element_by_name(
+          BAND_5G_PSK).get_attribute("value")
+    elif dict_5g["security"] == "wpa2":
+      dict_5g["radius_ip_addr"] = self.driver.find_element_by_name(
+          BAND_5G_RAD_IP).get_attribute("value")
+      dict_5g["radius_port"] = self.driver.find_element_by_name(
+          BAND_5G_RAD_PORT).get_attribute("value")
+      dict_2g["radius_secret"] = self.driver.find_element_by_name(
+          BAND_5G_RAD_KEY).get_attribute("value")
+    channel_field = self._get_webdriver_elements_for_channels(band)
+    ch_val = self.driver.find_element_by_name(channel_field).get_attribute(
+        "value")
+    channel = 0
+    for key, val in CHANNELS_5G.items():
+      if val == ch_val:
+        channel = key
+        break
+    self.ap_settings["5g"] = dict_5g.copy()
+    self.ap_settings["5g"]["channel"] = channel
+
+  def _update_ap_settings_6g_band(self):
+    """Read settings configured on 6g band.
+
+    Parameters Updated:
+      security type: wpa3-owe, wpa3-sae.
+      ssid: SSID of the wifi network.
+      password: password of the wifi network (if sae network).
+      channel: 6G band channel.
+    """
+    dict_6g = {}
+    dict_6g["security"] = self.driver.find_element_by_name(
+        BAND_6G_AUTH).get_attribute("value")
+    dict_6g["SSID"] = self.driver.find_element_by_name(
+        BAND_6G_SSID).get_attribute("value")
+    if dict_6g["security"] == "sae":
+      dict_6g["password"] = self.driver.find_element_by_name(
+          BAND_6G_PSK).get_attribute("value")
+    channel_field = self._get_webdriver_elements_for_channels(band)
+    ch_val = self.driver.find_element_by_name(channel_field).get_attribute(
+        "value")
+    channel = 0
+    for key, val in CHANNELS_6G.items():
+      if val == ch_val:
+        channel = key
+        break
+    self.ap_settings["6g"] = dict_6g.copy()
+    self.ap_settings["6g"]["channel"] = channel
+
+  def _update_ap_settings(self):
+    """Read AP settings of 2G, 5G and 6G bands.
+
+    This method reads the wifi network currently configured on any particular
+    band. The settings are updated to self.ap_settings object.
+    """
+    self.driver.refresh()
+    self._update_ap_settings_2g_band()
+    self._update_ap_settings_5g_band()
+    self._update_ap_settings_6g_band()
+
+  def _get_webdriver_elements_for_channels(self, band):
+    """Return webdriver elements for the band to configure channel.
+
+    Args:
+      band: str, Wifi band to configure. Ex: 2g, 5g, 6g.
+
+    Returns:
+      channel field for the specific band.
+    """
+    channel_field = BAND_2G_CHANNEL
+    if band == "5g":
+      channel_field = BAND_5G_CHANNEL
+    elif band == "6g":
+      channel_field = BAND_6G_CHANNEL
+    return channel_field
+
+  def _set_channel(self, band, channel):
+    """Configure channel on a specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+      channel: int, Channel to set.
+
+    Raises:
+      ValueError: An error occurred due to invalid band or configuration.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+    if (band == "2g" and channel not in CHANNELS_2G) or (
+        band == "5g" and
+        channel not in CHANNELS_5G) or (band == "6g" and
+                                        channel not in CHANNELS_6G):
+      raise ValueError("Channel %s is not supported in band %s" %
+                       (channel, band))
+    channel_field = self._get_webdriver_elements_for_channels(band)
+    channels_val_dict = CHANNELS_6G
+    if band == "2g":
+      channels_val_dict = CHANNELS_2G
+    elif band == "5g":
+      channels_val_dict = CHANNELS_5G
+    channel = channels_val_dict[channel]
+
+    # Set channel
+    if self.driver.find_element_by_name(channel_field).get_attribute(
+        "value") != channel:
+      css_selector = "select[name=%s]" % channel_field
+      Select(self.driver.find_element_by_css_selector(
+          css_selector)).select_by_value(channel)
+      time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+
+  def _configure_personal_network(self, band, auth, ssid=None, password=None):
+    """Configure wpa3 sae/wpa2 psk network on a specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+      auth: str, WPA2 PSK or WPA3 SAE security.
+      ssid: str, ssid of the wifi network.
+      password: str, password of the wifi network.
+
+    Raises:
+      ValueError: An error occurred due to invalid band or configuration.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+    if band == "6g" and auth == "psk2":
+      raise ValueError("AP doesn't support WPA2 PSK on 6g band.")
+    (auth_field, ssid_field,
+     psk_field) = self._get_webdriver_elements_for_personal_auth(band)
+
+    # configure personal network
+    css_selector = "select[name=%s]" % auth_field
+    Select(self.driver.find_element_by_css_selector(
+        css_selector)).select_by_value(auth)
+    time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+    if ssid:
+      self.driver.find_element_by_name(ssid_field).clear()
+      self.driver.find_element_by_name(ssid_field).send_keys(ssid)
+    if password:
+      self.driver.find_element_by_name(psk_field).clear()
+      self.driver.find_element_by_name(psk_field).send_keys(password)
+
+  def _configure_open_owe_network(self, band, auth, ssid=None):
+    """Configure wpa3 owe/open network on a specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+      auth: str, WPA2 PSK or WPA3 SAE security.
+      ssid: str, ssid of the wifi network.
+
+    Raises:
+      ValueError: An error occurred due to invalid band or configuration.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+    if band == "6g" and auth == "open":
+      raise ValueError("AP doesn't support open network on 6g band.")
+    if (band == "2g" or band == "5g") and auth == "owe":
+      raise ValueError("AP doesn't support OWE on 2g and 5g bands.")
+    (auth_field, ssid_field,
+     _) = self._get_webdriver_elements_for_personal_auth(band)
+
+    # Configure wifi network
+    css_selector = "select[name=%s]" % auth_field
+    Select(self.driver.find_element_by_css_selector(
+        css_selector)).select_by_value(auth)
+    time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+    if ssid:
+      self.driver.find_element_by_name(ssid_field).clear()
+      self.driver.find_element_by_name(ssid_field).send_keys(ssid)
+
+  def _configure_wpa2_ent_network(self, band, radius_ip, radius_port,
+                                  radius_secret, ssid=None):
+    """Configure wpa2 ent network on a specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g.
+      radius_ip: str, radius server ip addr.
+      radius_port: str, radius server port number.
+      radius_secret: str, radius server secret.
+      ssid: str, ssid of the wifi network.
+
+    Raises:
+      ValueError: An error occurred due to invalid band or configuration.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+    if band == "6g":
+      raise ValueError("6GHz doesn't support enterprise network on this AP.")
+    (auth_field, ssid_field,
+     _) = self._get_webdriver_elements_for_personal_auth(band)
+    (rad_ip_field, rad_port_field,
+     rad_key_field) = self._get_webdriver_elements_for_ent_auth(band)
+
+    # Set enterprise network
+    css_selector = "select[name=%s]" % auth_field
+    Select(self.driver.find_element_by_css_selector(
+        css_selector)).select_by_value("wpa2")
+    time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+    if ssid:
+      self.driver.find_element_by_name(ssid_field).clear()
+      self.driver.find_element_by_name(ssid_field).send_keys(ssid)
+    self.driver.find_element_by_name(rad_ip_field).clear()
+    self.driver.find_element_by_name(rad_ip_field).send_keys(radius_ip)
+    self.driver.find_element_by_name(rad_port_field).clear()
+    self.driver.find_element_by_name(rad_port_field).send_keys(radius_port)
+    self.driver.find_element_by_name(rad_key_field).clear()
+    self.driver.find_element_by_name(rad_key_field).send_keys(radius_secret)
+
+  def _get_webdriver_elements_for_personal_auth(self, band):
+    """Return webdriver elements for the band to configure personal auth.
+
+    Args:
+      band: str, Wifi band to configure. Ex: 2g, 5g, 6g.
+
+    Returns:
+      tuple of auth, ssid, psk field for the band.
+    """
+    auth_field = BAND_2G_AUTH
+    ssid_field = BAND_2G_SSID
+    psk_field = BAND_2G_PSK
+    if band == "5g":
+      auth_field = BAND_5G_AUTH
+      ssid_field = BAND_5G_SSID
+      psk_field = BAND_5G_PSK
+    elif band == "6g":
+      auth_field = BAND_6G_AUTH
+      ssid_field = BAND_6G_SSID
+      psk_field = BAND_6G_PSK
+    return (auth_field, ssid_field, psk_field)
+
+  def _get_webdriver_elements_for_ent_auth(self, band):
+    """Return webdriver elements for the band to configure ent auth.
+
+    Args:
+      band: str, Wifi band to configure. Ex: 2g, 5g, 6g.
+
+    Returns:
+      tuple of radius server IP, port, secret for the band.
+    """
+    rad_ip_field = BAND_2G_RAD_IP
+    rad_port_field = BAND_2G_RAD_PORT
+    rad_key_field = BAND_2G_RAD_KEY
+    if band == "5g":
+      rad_ip_field = BAND_5G_RAD_IP
+      rad_port_field = BAND_5G_RAD_PORT
+      rad_key_field = BAND_5G_RAD_KEY
+    return (rad_ip_field, rad_port_field, rad_key_field)
+
+  ### Methods to configure AP ###
+
+  def set_channel_and_apply(self, band, channel):
+    """Set channel for specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+      channel: int, Channel to set.
+    """
+    # Go back to General tab in advanced settings
+    self.driver.find_element_by_id(GENERAL_TAB).click()
+    self._wait_for_web_element(self.driver.find_element_by_id, SMART_CONNECT)
+
+    channel_field = self._get_webdriver_elements_for_channels(band)
+    self._set_channel(band, channel)
+    self.driver.find_element_by_id(APPLY_BUTTON_ID).click()
+    time.sleep(BROWSER_WAIT_LONG_TIMEOUT)
+    self._wait_for_web_element(self.driver.find_element_by_name,
+                               channel_field, "value", channel)
+    self._update_ap_settings()
+
+  def get_configured_channel(self, band):
+    """Get the channel configured on specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: eg, 5g, 6g.
+
+    Returns:
+      Channel configured on the band.
+
+    Raises:
+      ValueError: An error occurred due to invalid band.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+    return self.ap_settings[band]["channel"]
+
+  def configure_ap(self, network_dict):
+    """Configure AP with settings for different bands.
+
+    Args:
+      network_dict: dict, dictionary that holds configuration for each band.
+    """
+    # Go back to General tab in advanced settings
+    self.driver.refresh()
+    self.driver.find_element_by_id(GENERAL_TAB).click()
+    self._wait_for_web_element(self.driver.find_element_by_id, SMART_CONNECT)
+
+    # configure wireless settings
+    self.log.info("Network dictionary: %s" % network_dict)
+    for band in network_dict:
+      security = network_dict[band]["security"]
+      ssid = network_dict[band]["SSID"] if "SSID" in network_dict[
+          band] else None
+      password = network_dict[band]["password"] if "password" in network_dict[
+          band] else None
+      if security == "open" or security == "owe":
+        self._configure_open_owe_network(band, security, ssid)
+      elif security == "psk2" or security == "sae":
+        self._configure_personal_network(band, security, ssid, password)
+      elif network_dict[band]["security"] == "wpa2":
+        self._configure_wpa2_ent_network(
+            band,
+            network_dict[band]["radius_server_ip"],
+            network_dict[band]["radius_server_port"],
+            network_dict[band]["radius_server_secret"],
+            ssid)
+
+    for band in network_dict:
+      if "channel" in network_dict[band]:
+        self._set_channel(band, network_dict[band]["channel"])
+    self.driver.find_element_by_id(APPLY_BUTTON_ID).click()
+    time.sleep(BROWSER_WAIT_LONG_TIMEOUT)
+
+    # update ap settings
+    self._update_ap_settings()
+
+    # configure hidden or 11ax mode
+    for band in network_dict:
+      apply_settings = False
+      if "hidden" in network_dict[band]:
+        res = self._configure_hidden_network(band, network_dict[band]["hidden"])
+        apply_settings = apply_settings or res
+      if "11ax" in network_dict[band]:
+        res = self._configure_11ax_mode(band, network_dict[band]["11ax"])
+        apply_settings = apply_settings or res
+      if apply_settings:
+        self.driver.find_element_by_id(APPLY_BUTTON).click()
+        time.sleep(BROWSER_WAIT_VERY_LONG_TIMEOUT)
+
+  def get_wifi_network(self, band):
+    """Get wifi network configured on the AP for the specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+
+    Returns:
+      Wifi network as a dictionary.
+
+    Raises:
+      ValueError: An error occurred due to invalid band.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+    wifi_network = {}
+    wifi_network["SSID"] = self.ap_settings[band]["SSID"]
+    if "password" in self.ap_settings[band]:
+      wifi_network["password"] = self.ap_settings[band]["password"]
+    security = self.ap_settings[band]["security"]
+    if security == "sae" or security == "owe":
+      wifi_network["security"] = security
+    return wifi_network
+
+  def _configure_hidden_network(self, band, val):
+    """Configure hidden network for a specific band.
+
+    Args:
+      band: str, Wifi band to configure hidden network.
+      val: str, String value to configure.
+
+    Returns:
+      True if settings applied, False if not.
+
+    Raises:
+      ValueError: An error occurred due to invalid band.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+
+    # Go to Professional tab in advanced settings
+    self.driver.find_element_by_id(PROFESSIONAL_TAB).click()
+    self._wait_for_web_element(self.driver.find_element_by_id, HE_MODE_ID)
+
+    # Select the requested band from the drop down menu
+    css_selector = "select[name=%s]" % WL_UNIT
+    Select(
+        self.driver.find_element_by_css_selector(css_selector)).select_by_value(
+            WL_BAND_VALUE[band])  # (TODO: gmoturu@) find if selection worked
+    time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+
+    # Configure hidden network
+    state = True if val == "1" else False
+    return_result = False
+    if self.driver.find_element_by_name(WL_CLOSED).is_selected() != state:
+      css_selector = "input[name='%s'][value='%s']" % (WL_CLOSED, val)
+      self.driver.find_element_by_css_selector(css_selector).click()
+      time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+      return_result = True
+
+    return return_result
+
+  def configure_hidden_network_and_apply(self, band, state=True):
+    """Configure hidden network for a specific band.
+
+    Args:
+      band: str, Wifi band to configure hidden network.
+      state: bool, Set the wifi network as hidden if True, False if not.
+    """
+    val = "1" if state else "0"
+    if self._configure_hidden_network(band, val):
+      self.driver.find_element_by_id(APPLY_BUTTON).click()
+      time.sleep(BROWSER_WAIT_VERY_LONG_TIMEOUT)
+      if self.driver.find_element_by_name(WL_CLOSED).is_selected() != state:
+        raise ValueError("Failed to configure hidden network on band: %s" % band)
+
+      # Go back to General tab in advanced settings
+      self.driver.find_element_by_id(GENERAL_TAB).click()
+      self._wait_for_web_element(self.driver.find_element_by_id, SMART_CONNECT)
+
+  def _configure_11ax_mode(self, band, val):
+    """Configure 11ax mode on a specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+      val: str, String value to configure.
+
+    Returns:
+      True if settings are applied, False if not.
+
+    Raises:
+      ValueError: An error occurred due to invalid band.
+    """
+    band = band.lower()
+    if band not in VALID_BANDS:
+      raise ValueError("Band %s is not valid" % band)
+
+    # Go to Professional tab in advanced settings
+    self.driver.find_element_by_id(PROFESSIONAL_TAB).click()
+    self._wait_for_web_element(self.driver.find_element_by_id, HE_MODE_ID)
+
+    # Select the requested band from the drop down menu
+    css_selector = "select[name=%s]" % WL_UNIT
+    Select(
+        self.driver.find_element_by_css_selector(css_selector)).select_by_value(
+            WL_BAND_VALUE[band])  # (TODO: gmoturu@) find if selection worked
+    time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+
+    # Configure 11ax
+    return_result = False
+    if self.driver.find_element_by_name(WL_11AX).get_attribute(
+        "value") != val:
+      css_selector = "select[name=%s]" % WL_11AX
+      Select(self.driver.find_element_by_css_selector(
+          css_selector)).select_by_value(val)
+      time.sleep(BROWSER_WAIT_SHORT_TIMEOUT)
+      return_result = True
+
+    return return_result
+
+  def configure_11ax_mode_and_apply(self, band, state=True):
+    """Configure 11ax mode on a specific band.
+
+    Args:
+      band: str, Wifi band to check. Ex: 2g, 5g, 6g.
+      state: bool, Enable 11ax if True, disable if False
+    """
+    val = "1" if state else "0"
+    if self._configure_11ax_mode(band, val):
+      self.driver.find_element_by_id(APPLY_BUTTON).click()
+      time.sleep(BROWSER_WAIT_VERY_LONG_TIMEOUT)
+      self._wait_for_web_element(self.driver.find_element_by_name, WL_11AX,
+                                 "value", val)
+
+      # Go back to General tab in advanced settings
+      self.driver.find_element_by_id(GENERAL_TAB).click()
+      self._wait_for_web_element(self.driver.find_element_by_id, SMART_CONNECT)
+
+  def reset_to_default_ap_settings(self):
+    """Reset AP to the default settings."""
+    if self.default_ap_settings != self.ap_settings:
+      self.configure_ap(self.default_ap_settings)
+
diff --git a/acts/framework/acts/controllers/bits.py b/acts/framework/acts/controllers/bits.py
new file mode 100644
index 0000000..5194f92
--- /dev/null
+++ b/acts/framework/acts/controllers/bits.py
@@ -0,0 +1,306 @@
+"""Module managing the required definitions for using the bits power monitor"""
+
+from datetime import datetime
+import logging
+import os
+import time
+
+from acts import context
+from acts.controllers import power_metrics
+from acts.controllers import power_monitor
+from acts.controllers.bits_lib import bits_client
+from acts.controllers.bits_lib import bits_service
+from acts.controllers.bits_lib import bits_service_config as bsc
+
+MOBLY_CONTROLLER_CONFIG_NAME = 'Bits'
+ACTS_CONTROLLER_REFERENCE_NAME = 'bitses'
+
+
+def create(configs):
+    return [Bits(index, config) for (index, config) in enumerate(configs)]
+
+
+def destroy(bitses):
+    for bits in bitses:
+        bits.teardown()
+
+
+def get_info(bitses):
+    return [bits.config for bits in bitses]
+
+
+def _transform_name(bits_metric_name):
+    """Transform bits metrics names to a more succinct version.
+
+    Examples of bits_metrics_name as provided by the client:
+    - default_device.slider.C1_30__PP0750_L1S_VDD_G3D_M_P:mA,
+    - default_device.slider.C1_30__PP0750_L1S_VDD_G3D_M_P:mW,
+    - default_device.Monsoon.Monsoon:mA,
+    - default_device.Monsoon.Monsoon:mW,
+    - <device>.<collector>.<rail>:<unit>
+
+    Args:
+        bits_metric_name: A bits metric name.
+
+    Returns:
+        For monsoon metrics, and for backwards compatibility:
+          Monsoon:mA -> avg_current,
+          Monsoon:mW -> avg_power,
+
+        For everything else:
+          <rail>:mW -> <rail/rail>_avg_current
+          <rail>:mW -> <rail/rail>_avg_power
+          ...
+    """
+    prefix, unit = bits_metric_name.split(':')
+    rail = prefix.split('.')[-1]
+
+    if 'mW' == unit:
+        suffix = 'avg_power'
+    elif 'mA' == unit:
+        suffix = 'avg_current'
+    elif 'mV' == unit:
+        suffix = 'avg_voltage'
+    else:
+        logging.getLogger().warning('unknown unit type for unit %s' % unit)
+        suffix = ''
+
+    if 'Monsoon' == rail:
+        return suffix
+    elif suffix == '':
+        return rail
+    else:
+        return '%s_%s' % (rail, suffix)
+
+
+def _raw_data_to_metrics(raw_data_obj):
+    data = raw_data_obj['data']
+    metrics = []
+    for sample in data:
+        unit = sample['unit']
+        if 'Msg' == unit:
+            continue
+        elif 'mW' == unit:
+            unit_type = 'power'
+        elif 'mA' == unit:
+            unit_type = 'current'
+        elif 'mV' == unit:
+            unit_type = 'voltage'
+        else:
+            logging.getLogger().warning('unknown unit type for unit %s' % unit)
+            continue
+
+        name = _transform_name(sample['name'])
+        avg = sample['avg']
+        metrics.append(power_metrics.Metric(avg, unit_type, unit, name=name))
+
+    return metrics
+
+
+def _get_single_file(registry, key):
+    if key not in registry:
+        return None
+    entry = registry[key]
+    if isinstance(entry, str):
+        return entry
+    if isinstance(entry, list):
+        return None if len(entry) == 0 else entry[0]
+    raise ValueError('registry["%s"] is of unsupported type %s for this '
+                     'operation. Supported types are str and list.' % (
+                         key, type(entry)))
+
+
+class Bits(object):
+    def __init__(self, index, config):
+        """Creates an instance of a bits controller.
+
+        Args:
+            index: An integer identifier for this instance, this allows to
+                tell apart different instances in the case where multiple
+                bits controllers are being used concurrently.
+            config: The config as defined in the ACTS  BiTS controller config.
+                Expected format is:
+                {
+                    // optional
+                    'Monsoon':   {
+                        'serial_num': <serial number:int>,
+                        'monsoon_voltage': <voltage:double>
+                    }
+                    // optional
+                    'Kibble': [
+                        {
+                            'board': 'BoardName1',
+                            'connector': 'A',
+                            'serial': 'serial_1'
+                        },
+                        {
+                            'board': 'BoardName2',
+                            'connector': 'D',
+                            'serial': 'serial_2'
+                        }
+                    ]
+                }
+        """
+        self.index = index
+        self.config = config
+        self._service = None
+        self._client = None
+
+    def setup(self, *_, registry=None, **__):
+        """Starts a bits_service in the background.
+
+        This function needs to be
+        Args:
+            registry: A dictionary with files used by bits. Format:
+                {
+                    // required, string or list of strings
+                    bits_service: ['/path/to/bits_service']
+
+                    // required, string or list of strings
+                    bits_client: ['/path/to/bits.par']
+
+                    // needed for monsoon, string or list of strings
+                    lvpm_monsoon: ['/path/to/lvpm_monsoon.par']
+
+                    // needed for monsoon, string or list of strings
+                    hvpm_monsoon: ['/path/to/hvpm_monsoon.par']
+
+                    // needed for kibble, string or list of strings
+                    kibble_bin: ['/path/to/kibble.par']
+
+                    // needed for kibble, string or list of strings
+                    kibble_board_file: ['/path/to/phone_s.board']
+
+                    // optional, string or list of strings
+                    vm_file: ['/path/to/file.vm']
+                }
+
+                All fields in this dictionary can be either a string or a list
+                of strings. If lists are passed, only their first element is
+                taken into account. The reason for supporting lists but only
+                acting on their first element is for easier integration with
+                harnesses that handle resources as lists.
+        """
+        if registry is None:
+            registry = power_monitor.get_registry()
+        if 'bits_service' not in registry:
+            raise ValueError('No bits_service binary has been defined in the '
+                             'global registry.')
+        if 'bits_client' not in registry:
+            raise ValueError('No bits_client binary has been defined in the '
+                             'global registry.')
+
+        bits_service_binary = _get_single_file(registry, 'bits_service')
+        bits_client_binary = _get_single_file(registry, 'bits_client')
+        lvpm_monsoon_bin = _get_single_file(registry, 'lvpm_monsoon')
+        hvpm_monsoon_bin = _get_single_file(registry, 'hvpm_monsoon')
+        kibble_bin = _get_single_file(registry, 'kibble_bin')
+        kibble_board_file = _get_single_file(registry, 'kibble_board_file')
+        vm_file = _get_single_file(registry, 'vm_file')
+        config = bsc.BitsServiceConfig(self.config,
+                                       lvpm_monsoon_bin=lvpm_monsoon_bin,
+                                       hvpm_monsoon_bin=hvpm_monsoon_bin,
+                                       kibble_bin=kibble_bin,
+                                       kibble_board_file=kibble_board_file,
+                                       virtual_metrics_file=vm_file)
+        output_log = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            'bits_service_out_%s.txt' % self.index)
+        service_name = 'bits_config_%s' % self.index
+
+        self._service = bits_service.BitsService(config,
+                                                 bits_service_binary,
+                                                 output_log,
+                                                 name=service_name,
+                                                 timeout=3600 * 24)
+        self._service.start()
+        self._client = bits_client.BitsClient(bits_client_binary,
+                                              self._service,
+                                              config)
+        # this call makes sure that the client can interact with the server.
+        devices = self._client.list_devices()
+        logging.getLogger().debug(devices)
+
+    def disconnect_usb(self, *_, **__):
+        self._client.disconnect_usb()
+
+    def connect_usb(self, *_, **__):
+        self._client.connect_usb()
+
+    def measure(self, *_, measurement_args=None, **__):
+        """Blocking function that measures power through bits for the specified
+        duration. Results need to be consulted through other methods such as
+        get_metrics or export_to_csv.
+
+        Args:
+            measurement_args: A dictionary with the following structure:
+                {
+                   'duration': <seconds to measure for>
+                }
+        """
+        if measurement_args is None:
+            raise ValueError('measurement_args can not be left undefined')
+
+        duration = measurement_args.get('duration')
+        if duration is None:
+            raise ValueError(
+                'duration can not be left undefined within measurement_args')
+        self._client.start_collection()
+        time.sleep(duration)
+
+    def get_metrics(self, *_, timestamps=None, **__):
+        """Gets metrics for the segments delimited by the timestamps dictionary.
+
+        Args:
+            timestamps: A dictionary of the shape:
+                {
+                    'segment_name': {
+                        'start' : <milliseconds_since_epoch> or <datetime>
+                        'end': <milliseconds_since_epoch> or <datetime>
+                    }
+                    'another_segment': {
+                        'start' : <milliseconds_since_epoch> or <datetime>
+                        'end': <milliseconds_since_epoch> or <datetime>
+                    }
+                }
+        Returns:
+            A dictionary of the shape:
+                {
+                    'segment_name': <list of power_metrics.Metric>
+                    'another_segment': <list of power_metrics.Metric>
+                }
+        """
+        if timestamps is None:
+            raise ValueError('timestamps dictionary can not be left undefined')
+
+        metrics = {}
+
+        for segment_name, times in timestamps.items():
+            start = times['start']
+            end = times['end']
+
+            # bits accepts nanoseconds only, but since this interface needs to
+            # backwards compatible with monsoon which works with milliseconds we
+            # require to do a conversion from milliseconds to nanoseconds.
+            # The preferred way for new calls to this function should be using
+            # datetime instead which is unambiguous
+            if isinstance(start, (int, float)):
+                start = times['start'] * 1e6
+            if isinstance(end, (int, float)):
+                end = times['end'] * 1e6
+
+            self._client.add_marker(start, 'start - %s' % segment_name)
+            self._client.add_marker(end, 'end - %s' % segment_name)
+            raw_metrics = self._client.get_metrics(start, end)
+            metrics[segment_name] = _raw_data_to_metrics(raw_metrics)
+        return metrics
+
+    def release_resources(self):
+        self._client.stop_collection()
+
+    def teardown(self):
+        if self._service is None:
+            return
+
+        if self._service.service_state == bits_service.BitsServiceStates.STARTED:
+            self._service.stop()
diff --git a/acts_tests/tests/google/__init__.py b/acts/framework/acts/controllers/bits_lib/__init__.py
similarity index 100%
copy from acts_tests/tests/google/__init__.py
copy to acts/framework/acts/controllers/bits_lib/__init__.py
diff --git a/acts/framework/acts/controllers/bits_lib/bits_client.py b/acts/framework/acts/controllers/bits_lib/bits_client.py
new file mode 100644
index 0000000..4c8e740
--- /dev/null
+++ b/acts/framework/acts/controllers/bits_lib/bits_client.py
@@ -0,0 +1,299 @@
+#!/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 logging
+import os
+import uuid
+import tempfile
+import yaml
+from datetime import datetime
+
+from acts.libs.proc import job
+from acts import context
+
+
+class BitsClientError(Exception):
+    pass
+
+
+# An arbitrary large number of seconds.
+ONE_YEAR = str(3600 * 24 * 365)
+EPOCH = datetime.utcfromtimestamp(0)
+
+
+def _to_ns(timestamp):
+    """Returns the numerical value of a timestamp in nanoseconds since epoch.
+
+    Args:
+        timestamp: Either a number or a datetime.
+
+    Returns:
+        Rounded timestamp if timestamp is numeric, number of nanoseconds since
+        epoch if timestamp is instance of datetime.datetime.
+    """
+    if isinstance(timestamp, datetime):
+        return int((timestamp - EPOCH).total_seconds() * 1e9)
+    elif isinstance(timestamp, (float, int)):
+        return int(timestamp)
+    raise ValueError('%s can not be converted to a numerical representation of '
+                     'nanoseconds.' % type(timestamp))
+
+
+class _BitsCollection(object):
+    """Object that represents a bits collection
+
+    Attributes:
+        name: The name given to the collection.
+        markers_buffer: An array of un-flushed markers, each marker is
+        represented by a bi-dimensional tuple with the format
+        (<nanoseconds_since_epoch or datetime>, <text>).
+    """
+    def __init__(self, name):
+        self.name = name
+        self.markers_buffer = []
+
+    def add_marker(self, timestamp, marker_text):
+        self.markers_buffer.append((timestamp, marker_text))
+
+    def clear_markers_buffer(self):
+        self.markers_buffer.clear()
+
+
+class BitsClient(object):
+    """Helper class to issue bits' commands"""
+
+    def __init__(self, binary, service, service_config):
+        """Constructs a BitsClient.
+
+        Args:
+            binary: The location of the bits.par client binary.
+            service: A bits_service.BitsService object. The service is expected
+              to be previously setup.
+            service_config: The bits_service_config.BitsService object used to
+              start the service on service_port.
+        """
+        self._log = logging.getLogger()
+        self._binary = binary
+        self._service = service
+        self._server_config = service_config
+        self._active_collection = None
+        self._collections_counter = 0
+
+    def _acquire_monsoon(self):
+        """Gets hold of a Monsoon so no other processes can use it.
+        Only works if there is a monsoon."""
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--collector',
+               'Monsoon',
+               '--collector_cmd',
+               'acquire_monsoon']
+        self._log.info('acquiring monsoon')
+        job.run(cmd, timeout=10)
+
+    def _release_monsoon(self):
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--collector',
+               'Monsoon',
+               '--collector_cmd',
+               'release_monsoon']
+        self._log.info('releasing monsoon')
+        job.run(cmd, timeout=10)
+
+    def _export(self):
+        collection_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '%s.7z.bits' % self._active_collection.name)
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--name',
+               self._active_collection.name,
+               '--ignore_gaps',
+               '--export',
+               '--export_path',
+               collection_path]
+        self._log.info('exporting collection %s to %s',
+                       self._active_collection.name,
+                       collection_path)
+        job.run(cmd, timeout=600)
+
+    def _flush_markers(self):
+        for ts, marker in sorted(self._active_collection.markers_buffer,
+                                 key=lambda x: x[0]):
+            cmd = [self._binary,
+                   '--port',
+                   self._service.port,
+                   '--name',
+                   self._active_collection.name,
+                   '--log_ts',
+                   str(_to_ns(ts)),
+                   '--log',
+                   marker]
+            job.run(cmd, timeout=10)
+        self._active_collection.clear_markers_buffer()
+
+    def add_marker(self, timestamp, marker_text):
+        """Buffers a marker for the active collection.
+
+        Bits does not allow inserting markers with timestamps out of order.
+        The buffer of markers will be flushed when the collection is stopped to
+        ensure all the timestamps are input in order.
+
+        Args:
+            timestamp: Numerical nanoseconds since epoch or datetime.
+            marker_text: A string to label this marker with.
+        """
+        if not self._active_collection:
+            raise BitsClientError(
+                'markers can not be added without an active collection')
+        self._active_collection.add_marker(timestamp, marker_text)
+
+    def get_metrics(self, start, end):
+        """Extracts metrics for a period of time.
+
+        Args:
+            start: Numerical nanoseconds since epoch until the start of the
+            period of interest or datetime.
+            end: Numerical nanoseconds since epoch until the end of the
+            period of interest or datetime.
+        """
+        if not self._active_collection:
+            raise BitsClientError(
+                'metrics can not be collected without an active collection')
+
+        with tempfile.NamedTemporaryFile(prefix='bits_metrics') as tf:
+            cmd = [self._binary,
+                   '--port',
+                   self._service.port,
+                   '--name',
+                   self._active_collection.name,
+                   '--ignore_gaps',
+                   '--abs_start_time',
+                   str(_to_ns(start)),
+                   '--abs_stop_time',
+                   str(_to_ns(end)),
+                   '--aggregates_yaml_path',
+                   tf.name]
+            if self._server_config.has_virtual_metrics_file:
+                cmd = cmd + ['--vm_file', 'default']
+            job.run(cmd)
+            with open(tf.name) as mf:
+                self._log.debug(
+                    'bits aggregates for collection %s [%s-%s]: %s' % (
+                        self._active_collection.name, start, end,
+                        mf.read()))
+
+            with open(tf.name) as mf:
+                return yaml.safe_load(mf)
+
+    def disconnect_usb(self):
+        """Disconnects the monsoon's usb. Only works if there is a monsoon"""
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--collector',
+               'Monsoon',
+               '--collector_cmd',
+               'usb_disconnect']
+        self._log.info('disconnecting monsoon\'s usb')
+        job.run(cmd, timeout=10)
+
+    def start_collection(self, postfix=None):
+        """Indicates Bits to start a collection.
+
+        Args:
+            postfix: Optional argument that can be used to identify the
+            collection with.
+        """
+        if self._active_collection:
+            raise BitsClientError(
+                'Attempted to start a collection while there is still an '
+                'active one. Active collection: %s',
+                self._active_collection.name)
+        self._collections_counter = self._collections_counter + 1
+        # The name gets a random 8 characters salt suffix because the Bits
+        # client has a bug where files with the same name are considered to be
+        # the same collection and it won't load two files with the same name.
+        # b/153170987 b/153944171
+        if not postfix:
+            postfix = str(self._collections_counter)
+        postfix = '%s_%s' % (postfix, str(uuid.uuid4())[0:8])
+        self._active_collection = _BitsCollection(
+            'bits_collection_%s' % postfix)
+
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--name',
+               self._active_collection.name,
+               '--non_blocking',
+               '--time',
+               ONE_YEAR,
+               '--default_sampling_rate',
+               '1000',
+               '--disk_space_saver']
+        self._log.info('starting collection %s', self._active_collection.name)
+        job.run(cmd, timeout=10)
+
+    def connect_usb(self):
+        """Connects the monsoon's usb. Only works if there is a monsoon."""
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--collector',
+               'Monsoon',
+               '--collector_cmd',
+               'usb_connect']
+        self._log.info('connecting monsoon\'s usb')
+        job.run(cmd, timeout=10)
+
+    def stop_collection(self):
+        """Stops the active collection."""
+        if not self._active_collection:
+            raise BitsClientError(
+                'Attempted to stop a collection without starting one')
+        self._log.info('stopping collection %s', self._active_collection.name)
+        self._flush_markers()
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--name',
+               self._active_collection.name,
+               '--stop']
+        job.run(cmd)
+        self._export()
+        self._log.info('stopped collection %s', self._active_collection.name)
+        self._active_collection = None
+
+    def list_devices(self):
+        """Lists devices managed by the bits_server this client is connected
+        to.
+
+        Returns:
+            bits' output when called with --list devices.
+        """
+        cmd = [self._binary,
+               '--port',
+               self._service.port,
+               '--list',
+               'devices']
+        self._log.debug('listing devices')
+        result = job.run(cmd, timeout=20)
+        return result.stdout
diff --git a/acts/framework/acts/controllers/bits_lib/bits_service.py b/acts/framework/acts/controllers/bits_lib/bits_service.py
new file mode 100644
index 0000000..77cc5b6
--- /dev/null
+++ b/acts/framework/acts/controllers/bits_lib/bits_service.py
@@ -0,0 +1,223 @@
+#!/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 atexit
+import json
+import logging
+import os
+import re
+import signal
+import tempfile
+import time
+
+from enum import Enum
+
+from acts import context
+from acts.libs.proc import job
+from acts.libs.proc import process
+
+
+class BitsServiceError(Exception):
+    pass
+
+
+class BitsServiceStates(Enum):
+    NOT_STARTED = 'not-started'
+    STARTED = 'started'
+    STOPPED = 'stopped'
+
+
+class BitsService(object):
+    """Helper class to start and stop a bits service
+
+    Attributes:
+        port: When the service starts the port it was assigned to is made
+        available for external agents to reference to the background service.
+        config: The BitsServiceConfig used to configure this service.
+        name: A free form string.
+        service_state: A BitsServiceState that represents the service state.
+    """
+
+    def __init__(self, config, binary, output_log_path,
+                 name='bits_service_default',
+                 timeout=None):
+        """Creates a BitsService object.
+
+        Args:
+            config: A BitsServiceConfig.
+            described in go/pixel-bits/user-guide/service/configuration.md
+            binary: Path to a bits_service binary.
+            output_log_path: Full path to where the resulting logs should be
+            stored.
+            name: Optional string to identify this service by. This
+            is used as reference in logs to tell this service apart from others
+            running in parallel.
+            timeout: Maximum time in seconds the service should be allowed
+            to run in the background after start. If left undefined the service
+            in the background will not time out.
+        """
+        self.name = name
+        self.port = None
+        self.config = config
+        self.service_state = BitsServiceStates.NOT_STARTED
+        self._timeout = timeout
+        self._binary = binary
+        self._log = logging.getLogger()
+        self._process = None
+        self._output_log = open(output_log_path, 'w')
+        self._collections_dir = tempfile.TemporaryDirectory(
+            prefix='bits_service_collections_dir_')
+        self._cleaned_up = False
+        atexit.register(self._atexit_cleanup)
+
+    def _atexit_cleanup(self):
+        if not self._cleaned_up:
+            self._log.error('Cleaning up bits_service %s at exit.', self.name)
+            self._cleanup()
+
+    def _write_extra_debug_logs(self):
+        dmesg_log = '%s.dmesg.txt' % self._output_log.name
+        dmesg = job.run(['dmesg', '-e'], ignore_status=True)
+        with open(dmesg_log, 'w') as f:
+            f.write(dmesg.stdout)
+
+        free_log = '%s.free.txt' % self._output_log.name
+        free = job.run(['free', '-m'], ignore_status=True)
+        with open(free_log, 'w') as f:
+            f.write(free.stdout)
+
+        df_log = '%s.df.txt' % self._output_log.name
+        df = job.run(['df', '-h'], ignore_status=True)
+        with open(df_log, 'w') as f:
+            f.write(df.stdout)
+
+    def _cleanup(self):
+        self._write_extra_debug_logs()
+        self.port = None
+        self._collections_dir.cleanup()
+        if self._process and self._process.is_running():
+            self._process.signal(signal.SIGINT)
+            self._log.debug('SIGINT sent to bits_service %s.' % self.name)
+            self._process.wait(kill_timeout=60.0)
+            self._log.debug('bits_service %s has been stopped.' % self.name)
+        self._output_log.close()
+        if self.config.has_monsoon:
+            job.run([self.config.monsoon_config.monsoon_binary,
+                     '--serialno',
+                     str(self.config.monsoon_config.serial_num),
+                     '--usbpassthrough',
+                     'on'],
+                    timeout=10)
+        self._cleaned_up = True
+
+    def _service_started_listener(self, line):
+        if self.service_state is BitsServiceStates.STARTED:
+            return
+        if 'Started server!' in line and self.port is not None:
+            self.service_state = BitsServiceStates.STARTED
+
+    PORT_PATTERN = re.compile(r'.*Server listening on .*:(\d+)\.$')
+
+    def _service_port_listener(self, line):
+        if self.port is not None:
+            return
+        match = self.PORT_PATTERN.match(line)
+        if match:
+            self.port = match.group(1)
+
+    def _output_callback(self, line):
+        self._output_log.write(line)
+        self._output_log.write('\n')
+        self._service_port_listener(line)
+        self._service_started_listener(line)
+
+    def _trigger_background_process(self, binary):
+        config_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            '%s.config.json' % self.name)
+        with open(config_path, 'w') as f:
+            f.write(json.dumps(self.config.config_dic, indent=2))
+
+        cmd = [binary,
+               '--port',
+               '0',
+               '--collections_folder',
+               self._collections_dir.name,
+               '--collector_config_file',
+               config_path]
+
+        # bits_service only works on linux systems, therefore is safe to assume
+        # that 'timeout' will be available.
+        if self._timeout:
+            cmd = ['timeout',
+                   '--signal=SIGTERM',
+                   '--kill-after=60',
+                   str(self._timeout)] + cmd
+
+        self._process = process.Process(cmd)
+        self._process.set_on_output_callback(self._output_callback)
+        self._process.set_on_terminate_callback(self._on_terminate)
+        self._process.start()
+
+    def _on_terminate(self, *_):
+        self._log.error('bits_service %s stopped unexpectedly.', self.name)
+        self._cleanup()
+
+    def start(self):
+        """Starts the bits service in the background.
+
+        This function blocks until the background service signals that it has
+        successfully started. A BitsServiceError is raised if the signal is not
+        received.
+        """
+        if self.service_state is BitsServiceStates.STOPPED:
+            raise BitsServiceError(
+                'bits_service %s was already stopped. A stopped'
+                ' service can not be started again.' % self.name)
+
+        if self.service_state is BitsServiceStates.STARTED:
+            raise BitsServiceError(
+                'bits_service %s has already been started.' % self.name)
+
+        self._log.info('starting bits_service %s', self.name)
+        self._trigger_background_process(self._binary)
+
+        # wait 40 seconds for the service to be ready.
+        max_startup_wait = time.time() + 40
+        while time.time() < max_startup_wait:
+            if self.service_state is BitsServiceStates.STARTED:
+                self._log.info('bits_service %s started on port %s', self.name,
+                               self.port)
+                return
+            time.sleep(0.1)
+
+        self._log.error('bits_service %s did not start on time, starting '
+                        'service teardown and raising a BitsServiceError.')
+        self._cleanup()
+        raise BitsServiceError(
+            'bits_service %s did not start successfully' % self.name)
+
+    def stop(self):
+        """Stops the bits service."""
+        if self.service_state is BitsServiceStates.STOPPED:
+            raise BitsServiceError(
+                'bits_service %s has already been stopped.' % self.name)
+        port = self.port
+        self._log.info('stopping bits_service %s on port %s', self.name, port)
+        self.service_state = BitsServiceStates.STOPPED
+        self._cleanup()
+        self._log.info('bits_service %s on port %s was stopped', self.name,
+                       port)
diff --git a/acts/framework/acts/controllers/bits_lib/bits_service_config.py b/acts/framework/acts/controllers/bits_lib/bits_service_config.py
new file mode 100644
index 0000000..b17ff3b
--- /dev/null
+++ b/acts/framework/acts/controllers/bits_lib/bits_service_config.py
@@ -0,0 +1,249 @@
+#!/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 copy
+
+DEFAULT_MONSOON_CONFIG_DICT = {
+    'enabled': 1,
+    'type': 'monsooncollector',
+    'monsoon_reset': 0,
+    # maximum monsoon sample rate that works best for both lvpm and hvpm
+    'sampling_rate': 1000,
+}
+
+
+class _BitsMonsoonConfig(object):
+    """Helper object to construct a bits_service config from a monsoon config as
+    defined for the bits controller config and required additional resources,
+    such as paths to executables.
+
+    The format for the bits_service's monsoon configuration is explained at:
+    http://go/pixel-bits/user-guide/service/collectors/monsoon
+
+    Attributes:
+        config_dic: A bits_service's monsoon configuration as a python
+        dictionary.
+    """
+
+    def __init__(self, monsoon_config, lvpm_monsoon_bin=None,
+                 hvpm_monsoon_bin=None):
+        """Constructs _BitsServiceMonsoonConfig.
+
+        Args:
+            monsoon_config: The monsoon config as defined in the
+                ACTS Bits controller config. Expected format is:
+                  { 'serial_num': <serial number:int>,
+                    'monsoon_voltage': <voltage:double> }
+            lvpm_monsoon_bin: Binary file to interact with low voltage monsoons.
+                Needed if the monsoon is a lvpm monsoon (serial number lower
+                than 20000).
+            hvpm_monsoon_bin: Binary file to interact with high voltage
+                monsoons. Needed if the monsoon is a hvpm monsoon (serial number
+                greater than 20000).
+        """
+        if 'serial_num' not in monsoon_config:
+            raise ValueError(
+                'Monsoon serial_num can not be undefined. Received '
+                'config was: %s' % monsoon_config)
+        if 'monsoon_voltage' not in monsoon_config:
+            raise ValueError('Monsoon voltage can not be undefined. Received '
+                             'config was: %s' % monsoon_config)
+
+        self.serial_num = monsoon_config['serial_num']
+        self.monsoon_voltage = monsoon_config['monsoon_voltage']
+
+        self.config_dic = copy.deepcopy(DEFAULT_MONSOON_CONFIG_DICT)
+        if float(self.serial_num) >= 20000:
+            self.config_dic['hv_monsoon'] = 1
+            if hvpm_monsoon_bin is None:
+                raise ValueError('hvpm_monsoon binary is needed but was None. '
+                                 'Received config was: %s' % monsoon_config)
+            self.monsoon_binary = hvpm_monsoon_bin
+        else:
+            self.config_dic['hv_monsoon'] = 0
+            if lvpm_monsoon_bin is None:
+                raise ValueError('lvpm_monsoon binary is needed but was None. '
+                                 'Received config was: %s' % monsoon_config)
+            self.monsoon_binary = lvpm_monsoon_bin
+
+        self.config_dic['monsoon_binary_path'] = self.monsoon_binary
+        self.config_dic['monsoon_voltage'] = self.monsoon_voltage
+        self.config_dic['serial_num'] = self.serial_num
+
+
+DEFAULT_KIBBLES_BOARD_CONFIG = {
+    'enabled': 1,
+    'type': 'kibblecollector',
+    'attached_kibbles': {}
+}
+
+DEFAULT_KIBBLE_CONFIG = {
+    'ultra_channels_current_hz': 976.5625,
+    'ultra_channels_voltage_hz': 976.5625,
+    'high_channels_current_hz': 976.5625,
+    'high_channels_voltage_hz': 976.5625
+}
+
+
+class _BitsKibblesConfig(object):
+    def __init__(self, kibbles_config, kibble_bin, kibble_board_file):
+        """Constructs _BitsKibblesConfig.
+
+        Args:
+            kibbles_config: A list of compacted kibble boards descriptions.
+                Expected format is:
+                    [{
+                        'board': 'BoardName1',
+                        'connector': 'A',
+                        'serial': 'serial_1'
+                     },
+                    {
+                        'board': 'BoardName2',
+                        'connector': 'D',
+                        'serial': 'serial_2'
+                    }]
+                More details can be found at go/acts-bits.
+            kibble_bin: Binary file to interact with kibbles.
+            kibble_board_file: File describing the distribution of rails on a
+                kibble. go/kibble#setting-up-bits-board-files
+        """
+
+        if not isinstance(kibbles_config, list):
+            raise ValueError(
+                'kibbles_config must be a list. Got %s.' % kibbles_config)
+
+        if kibble_bin is None:
+            raise ValueError('Kibbles were present in the config but no '
+                             'kibble_bin was provided')
+        if kibble_board_file is None:
+            raise ValueError('Kibbles were present in the config but no '
+                             'kibble_board_file was provided')
+
+        self.boards_configs = {}
+
+        for kibble in kibbles_config:
+            if 'board' not in kibble:
+                raise ValueError('An individual kibble config must have a '
+                                 'board')
+            if 'connector' not in kibble:
+                raise ValueError('An individual kibble config must have a '
+                                 'connector')
+            if 'serial' not in kibble:
+                raise ValueError('An individual kibble config must have a '
+                                 'serial')
+
+            board = kibble['board']
+            connector = kibble['connector']
+            serial = kibble['serial']
+            if board not in self.boards_configs:
+                self.boards_configs[board] = copy.deepcopy(
+                    DEFAULT_KIBBLES_BOARD_CONFIG)
+                self.boards_configs[board][
+                    'board_file'] = kibble_board_file
+                self.boards_configs[board]['kibble_py'] = kibble_bin
+            kibble_config = copy.deepcopy(DEFAULT_KIBBLE_CONFIG)
+            kibble_config['connector'] = connector
+            self.boards_configs[board]['attached_kibbles'][
+                serial] = kibble_config
+
+
+DEFAULT_SERVICE_CONFIG_DICT = {
+    'devices': {
+        'default_device': {
+            'enabled': 1,
+            'collectors': {}
+        }
+    }
+}
+
+
+class BitsServiceConfig(object):
+    """Helper object to construct a bits_service config from a bits controller
+    config and required additional resources, such as paths to executables.
+
+    The format for bits_service's configuration is explained in:
+    go/pixel-bits/user-guide/service/configuration.md
+
+    Attributes:
+        config_dic: A bits_service configuration as a python dictionary.
+    """
+
+    def __init__(self, controller_config, lvpm_monsoon_bin=None,
+                 hvpm_monsoon_bin=None, kibble_bin=None,
+                 kibble_board_file=None, virtual_metrics_file=None):
+        """Creates a BitsServiceConfig.
+
+        Args:
+            controller_config: The config as defined in the ACTS  BiTS
+                controller config. Expected format is:
+                {
+                    // optional
+                    'Monsoon':   {
+                        'serial_num': <serial number:int>,
+                        'monsoon_voltage': <voltage:double>
+                    }
+                    // optional
+                    'Kibble': [
+                        {
+                            'board': 'BoardName1',
+                            'connector': 'A',
+                            'serial': 'serial_1'
+                        },
+                        {
+                            'board': 'BoardName2',
+                            'connector': 'D',
+                            'serial': 'serial_2'
+                        }
+                    ]
+                }
+            lvpm_monsoon_bin: Binary file to interact with low voltage monsoons.
+                Needed if the monsoon is a lvpm monsoon (serial number lower
+                than 20000).
+            hvpm_monsoon_bin: Binary file to interact with high voltage
+                monsoons. Needed if the monsoon is a hvpm monsoon (serial number
+                greater than 20000).
+            kibble_bin: Binary file to interact with kibbles.
+            kibble_board_file: File describing the distribution of rails on a
+                kibble. go/kibble#setting-up-bits-board-files
+            virtual_metrics_file: A list of virtual metrics files to add
+                data aggregates on top of regular channel aggregates.
+                go/pixel-bits/user-guide/virtual-metrics
+        """
+        self.config_dic = copy.deepcopy(DEFAULT_SERVICE_CONFIG_DICT)
+        self.has_monsoon = False
+        self.has_kibbles = False
+        self.has_virtual_metrics_file = False
+        self.monsoon_config = None
+        self.kibbles_config = None
+        if 'Monsoon' in controller_config:
+            self.has_monsoon = True
+            self.monsoon_config = _BitsMonsoonConfig(
+                controller_config['Monsoon'],
+                lvpm_monsoon_bin,
+                hvpm_monsoon_bin)
+            self.config_dic['devices']['default_device']['collectors'][
+                'Monsoon'] = self.monsoon_config.config_dic
+        if 'Kibbles' in controller_config:
+            self.has_kibbles = True
+            self.kibbles_config = _BitsKibblesConfig(
+                controller_config['Kibbles'],
+                kibble_bin, kibble_board_file)
+            self.config_dic['devices']['default_device']['collectors'].update(
+                self.kibbles_config.boards_configs)
+            if virtual_metrics_file is not None:
+                self.config_dic['devices']['default_device'][
+                    'vm_files'] = [virtual_metrics_file]
+                self.has_virtual_metrics_file = True
diff --git a/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py
index 6d67b12..1784315 100644
--- a/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py
@@ -20,9 +20,6 @@
 class LteCaSimulation(LteSimulation.LteSimulation):
     """ Carrier aggregation LTE simulation. """
 
-    # Configuration dictionary keys
-    PARAM_CA = 'ca'
-
     # Test config keywords
     KEY_FREQ_BANDS = "freq_bands"
 
@@ -63,285 +60,72 @@
         self.freq_bands = test_config.get(self.KEY_FREQ_BANDS, True)
 
     def configure(self, parameters):
-        """ Configures simulation using a dictionary of parameters.
-
-        Processes LTE CA configuration parameters.
+        """ Configures PCC and SCCs using a dictionary of parameters.
 
         Args:
-            parameters: a configuration dictionary
+            parameters: a list of configuration dictionaris
         """
-        # Get the CA band configuration
-        if self.PARAM_CA not in parameters:
-            raise ValueError(
-                "The config dictionary must include key '{}' with the CA "
-                "config. For example: ca_3c7c28a".format(self.PARAM_CA))
-
-        # Carrier aggregation configurations are indicated with the band numbers
-        # followed by the CA classes in a single string. For example, for 5 CA
-        # using 3C 7C and 28A the parameter value should be 3c7c28a.
-        ca_configs = re.findall(r'(\d+[abcABC])', parameters[self.PARAM_CA])
-
-        if not ca_configs:
-            raise ValueError(
-                "The CA configuration has to be indicated with one string as "
-                "in the following example: 3c7c28a".format(self.PARAM_CA))
-
-        # Initialize the secondary cells
-        bands = []
-        for ca in ca_configs:
-            ca_class = ca[-1]
-            band = ca[:-1]
-            bands.append(band)
-            if ca_class.upper() == 'B' or ca_class.upper() == 'C':
-                # Class B and C means two carriers with the same band
-                bands.append(band)
-        self.simulator.set_band_combination(bands)
-
-        # Count the number of carriers in the CA combination
-        self.num_carriers = 0
-        for ca in ca_configs:
-            ca_class = ca[-1]
-            # Class C means that there are two contiguous carriers, while other
-            # classes are a single one.
-            if ca_class.upper() == 'C':
-                self.num_carriers += 2
-            else:
-                self.num_carriers += 1
-
-        # Create an array of configuration objects to set up the base stations.
-        new_configs = [self.BtsConfig() for _ in range(self.num_carriers)]
-
-        # Save the bands to the bts config objects
-        bts_index = 0
-        for ca in ca_configs:
-            ca_class = ca[-1]
-            band = ca[:-1]
-
-            new_configs[bts_index].band = band
-            bts_index += 1
-
-            if ca_class.upper() == 'B' or ca_class.upper() == 'C':
-                # Class B and C means two carriers with the same band
-                new_configs[bts_index].band = band
-                bts_index += 1
-
-        # Get the bw for each carrier
-        # This is an optional parameter, by default the maximum bandwidth for
-        # each band will be selected.
-
-        if self.PARAM_BW not in parameters:
-            raise ValueError(
-                "The config dictionary must include the '{}' key.".format(
-                    self.PARAM_BW))
-
-        values = parameters[self.PARAM_BW]
-
-        bts_index = 0
-
-        for ca in ca_configs:
-
-            band = int(ca[:-1])
-            ca_class = ca[-1]
-
-            if values:
-                bw = int(values[bts_index])
-            else:
-                bw = max(self.allowed_bandwidth_dictionary[band])
-
-            new_configs[bts_index].bandwidth = bw
-            bts_index += 1
-
-            if ca_class.upper() == 'C':
-
-                new_configs[bts_index].bandwidth = bw
-
-                # Calculate the channel number for the second carrier to be
-                # contiguous to the first one
-                new_configs[bts_index].dl_channel = int(
-                    self.LOWEST_DL_CN_DICTIONARY[int(band)] + bw * 10 - 2)
-
-                bts_index += 1
-
-        # Get the MIMO mode for each carrier
-
-        if self.PARAM_MIMO not in parameters:
-            raise ValueError(
-                "The key '{}' has to be included in the config dictionary "
-                "with a list including the MIMO mode for each carrier.".format(
-                    self.PARAM_MIMO))
-
-        mimo_values = parameters[self.PARAM_MIMO]
-
-        if len(mimo_values) != self.num_carriers:
-            raise ValueError(
-                "The value of '{}' must be a list of MIMO modes with a length "
-                "equal to the number of carriers.".format(self.PARAM_MIMO))
-
-        for bts_index in range(self.num_carriers):
-
-            # Parse and set the requested MIMO mode
-
-            for mimo_mode in LteSimulation.MimoMode:
-                if mimo_values[bts_index] == mimo_mode.value:
-                    requested_mimo = mimo_mode
-                    break
-            else:
+        new_cell_list = []
+        for cell in parameters:
+            if self.PARAM_BAND not in cell:
                 raise ValueError(
-                    "The mimo mode must be one of %s." %
-                    {elem.value
-                     for elem in LteSimulation.MimoMode})
+                    "The configuration dictionary must include a key '{}' with "
+                    "the required band number.".format(self.PARAM_BAND))
 
-            if (requested_mimo == LteSimulation.MimoMode.MIMO_4x4
-                    and not self.simulator.LTE_SUPPORTS_4X4_MIMO):
-                raise ValueError("The test requires 4x4 MIMO, but that is not "
-                                 "supported by the MD8475A callbox.")
+            band = cell[self.PARAM_BAND]
 
-            new_configs[bts_index].mimo_mode = requested_mimo
+            if isinstance(band, str) and not band.isdigit():
+                ca_class = band[-1].upper()
+                band_num = int(band[:-1])
 
-            # Parse and set the requested TM
-            # This is an optional parameter, by the default value depends on the
-            # MIMO mode for each carrier
-            if self.PARAM_TM in parameters:
-                tm_values = parameters[self.PARAM_TM]
-                if len(tm_values) < bts_index + 1:
-                    raise ValueError(
-                        'The number of elements in the transmission mode list '
-                        'must be equal to the number of carriers.')
-                for tm in LteSimulation.TransmissionMode:
-                    if tm_values[bts_index] == tm.value[2:]:
-                        requested_tm = tm
-                        break
+                if ca_class in ['A', 'C']:
+                    # Remove the CA class label and add the cell
+                    cell[self.PARAM_BAND].band = band_num
+                    new_cell_list.append(cell)
+                elif ca_class == 'B':
+                    raise RuntimeError('Class B LTE CA not supported.')
                 else:
-                    raise ValueError(
-                        "The TM must be one of %s." %
-                        {elem.value
-                         for elem in LteSimulation.MimoMode})
+                    raise ValueError('Invalid band value: ' + band)
+
+                # Class C means that there are two contiguous carriers
+                if ca_class == 'C':
+                    new_cell_list.append(cell)
+                    bw = int(cell[self.PARAM_BW])
+                    new_cell_list[-1].dl_earfcn = int(
+                        self.LOWEST_DL_CN_DICTIONARY[band_num] + bw * 10 - 2)
             else:
-                # Provide default values if the TM parameter is not set
-                if requested_mimo == LteSimulation.MimoMode.MIMO_1x1:
-                    requested_tm = LteSimulation.TransmissionMode.TM1
-                else:
-                    requested_tm = LteSimulation.TransmissionMode.TM3
+                # The band is just a number, so just add it to the list.
+                new_cell_list.append(cell)
 
-            new_configs[bts_index].transmission_mode = requested_tm
+        self.simulator.set_band_combination(
+            [c[self.PARAM_BAND] for c in new_cell_list])
 
-            self.log.info("Cell {} will be set to {} and {} MIMO.".format(
-                bts_index + 1, requested_tm.value, requested_mimo.value))
+        self.num_carriers = len(new_cell_list)
 
-        # Get uplink power
+        # Setup the base station with the obtained configuration and then save
+        # these parameters in the current configuration object
+        for bts_index in range(self.num_carriers):
+            cell_config = self.configure_lte_cell(parameters[bts_index])
+            self.simulator.configure_bts(cell_config, bts_index)
+            self.bts_configs[bts_index].incorporate(cell_config)
 
-        ul_power = self.get_uplink_power_from_parameters(parameters)
+        # Now that the band is set, calibrate the link if necessary
+        self.load_pathloss_if_required()
+
+        # Get uplink power from primary carrier
+        ul_power = self.get_uplink_power_from_parameters(parameters[0])
 
         # Power is not set on the callbox until after the simulation is
         # started. Saving this value in a variable for later
         self.sim_ul_power = ul_power
 
-        # Get downlink power
-
-        dl_power = self.get_downlink_power_from_parameters(parameters)
+        # Get downlink power from primary carrier
+        dl_power = self.get_downlink_power_from_parameters(parameters[0])
 
         # Power is not set on the callbox until after the simulation is
         # started. Saving this value in a variable for later
         self.sim_dl_power = dl_power
 
-        # Setup scheduling mode
-        if self.PARAM_SCHEDULING not in parameters:
-            scheduling = LteSimulation.SchedulingMode.STATIC
-            self.log.warning(
-                "Key '{}' is not set in the config dictionary. Setting to "
-                "{} by default.".format(scheduling.value,
-                                        self.PARAM_SCHEDULING))
-        else:
-            for scheduling_mode in LteSimulation.SchedulingMode:
-                if (parameters[self.PARAM_SCHEDULING].upper() ==
-                        scheduling_mode.value):
-                    scheduling = scheduling_mode
-                    break
-            else:
-                raise ValueError(
-                    "Key '{}' must have a one of the following values: {}.".
-                    format(
-                        self.PARAM_SCHEDULING,
-                        {elem.value
-                         for elem in LteSimulation.SchedulingMode}))
-
-        for bts_index in range(self.num_carriers):
-            new_configs[bts_index].scheduling_mode = scheduling
-
-        if scheduling == LteSimulation.SchedulingMode.STATIC:
-            if self.PARAM_PATTERN not in parameters:
-                self.log.warning(
-                    "The '{}' key was not set, using 100% RBs for both "
-                    "DL and UL. To set the percentages of total RBs include "
-                    "the '{}' key with a list of two ints indicating downlink and uplink percentages."
-                    .format(self.PARAM_PATTERN, self.PARAM_PATTERN))
-                dl_pattern = 100
-                ul_pattern = 100
-            else:
-                values = parameters[self.PARAM_PATTERN]
-                dl_pattern = int(values[0])
-                ul_pattern = int(values[1])
-
-            if (dl_pattern, ul_pattern) not in [(0, 100), (100, 0),
-                                                (100, 100)]:
-                raise ValueError(
-                    "Only full RB allocation for DL or UL is supported in CA "
-                    "sims. The allowed combinations are 100/0, 0/100 and "
-                    "100/100.")
-
-            for bts_index in range(self.num_carriers):
-
-                # Look for a DL MCS configuration in the test parameters. If it
-                # is not present, use a default value.
-                if self.PARAM_DL_MCS in parameters:
-                    mcs_dl = int(parameters[self.PARAM_DL_MCS])
-                else:
-                    self.log.warning(
-                        'The config dictionary does not include the {} key. '
-                        'Setting to the max value by default'.format(
-                            self.PARAM_DL_MCS))
-
-                    if new_configs[bts_index].dl_256_qam_enabled and \
-                        new_configs[bts_index].bandwidth == 1.4:
-                        mcs_dl = 26
-                    elif (not new_configs[bts_index].dl_256_qam_enabled
-                          and new_configs[bts_index].mac_padding
-                          and new_configs[bts_index].bandwidth != 1.4):
-                        mcs_dl = 28
-                    else:
-                        mcs_dl = 27
-
-                # Look for an UL MCS configuration in the test parameters. If it
-                # is not present, use a default value.
-                if self.PARAM_UL_MCS in parameters:
-                    mcs_ul = int(parameters[self.PARAM_UL_MCS])
-                else:
-                    self.log.warning(
-                        'The config dictionary does not include the {} key. '
-                        'Setting to the max value by default'.format(
-                            self.PARAM_UL_MCS))
-
-                    if new_configs[bts_index].ul_64_qam_enabled:
-                        mcs_ul = 28
-                    else:
-                        mcs_ul = 23
-
-                dl_rbs, ul_rbs = self.allocation_percentages_to_rbs(
-                    new_configs[bts_index].bandwidth,
-                    new_configs[bts_index].transmission_mode, dl_pattern,
-                    ul_pattern)
-
-                new_configs[bts_index].dl_rbs = dl_rbs
-                new_configs[bts_index].ul_rbs = ul_rbs
-                new_configs[bts_index].dl_mcs = mcs_dl
-                new_configs[bts_index].ul_mcs = mcs_ul
-
-        # Setup the base stations with the obtained configurations and then save
-        # these parameters in the current configuration objects
-        for bts_index in range(len(new_configs)):
-            self.simulator.configure_bts(new_configs[bts_index], bts_index)
-            self.bts_configs[bts_index].incorporate(new_configs[bts_index])
-
         # Now that the band is set, calibrate the link for the PCC if necessary
         self.load_pathloss_if_required()
 
diff --git a/acts/framework/acts/controllers/cellular_lib/LteSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteSimulation.py
index 71231d6..defd5d4 100644
--- a/acts/framework/acts/controllers/cellular_lib/LteSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/LteSimulation.py
@@ -525,6 +525,44 @@
         Args:
             parameters: a configuration dictionary
         """
+        # Setup band
+        if self.PARAM_BAND not in parameters:
+            raise ValueError(
+                "The configuration dictionary must include a key '{}' with "
+                "the required band number.".format(self.PARAM_BAND))
+
+        self.simulator.set_band_combination([parameters[self.PARAM_BAND]])
+
+        new_config = self.configure_lte_cell(parameters)
+
+        # Get uplink power
+        ul_power = self.get_uplink_power_from_parameters(parameters)
+
+        # Power is not set on the callbox until after the simulation is
+        # started. Saving this value in a variable for later
+        self.sim_ul_power = ul_power
+
+        # Get downlink power
+        dl_power = self.get_downlink_power_from_parameters(parameters)
+
+        # Power is not set on the callbox until after the simulation is
+        # started. Saving this value in a variable for later
+        self.sim_dl_power = dl_power
+
+        # Setup the base station with the obtained configuration and then save
+        # these parameters in the current configuration object
+        self.simulator.configure_bts(new_config)
+        self.primary_config.incorporate(new_config)
+
+        # Now that the band is set, calibrate the link if necessary
+        self.load_pathloss_if_required()
+
+    def configure_lte_cell(self, parameters):
+        """ Configures an LTE cell using a dictionary of parameters.
+
+        Args:
+            parameters: a configuration dictionary
+        """
         # Instantiate a new configuration object
         new_config = self.BtsConfig()
 
@@ -535,7 +573,6 @@
                 "the required band number.".format(self.PARAM_BAND))
 
         new_config.band = parameters[self.PARAM_BAND]
-        self.simulator.set_band_combination([new_config.band])
 
         if not self.PARAM_DL_EARFCN in parameters:
             band = int(new_config.band)
@@ -801,29 +838,7 @@
                     'The {} key has to be followed by the paging cycle '
                     'duration in milliseconds.'.format(self.PARAM_PAGING))
 
-        # Get uplink power
-
-        ul_power = self.get_uplink_power_from_parameters(parameters)
-
-        # Power is not set on the callbox until after the simulation is
-        # started. Saving this value in a variable for later
-        self.sim_ul_power = ul_power
-
-        # Get downlink power
-
-        dl_power = self.get_downlink_power_from_parameters(parameters)
-
-        # Power is not set on the callbox until after the simulation is
-        # started. Saving this value in a variable for later
-        self.sim_dl_power = dl_power
-
-        # Setup the base station with the obtained configuration and then save
-        # these parameters in the current configuration object
-        self.simulator.configure_bts(new_config)
-        self.primary_config.incorporate(new_config)
-
-        # Now that the band is set, calibrate the link if necessary
-        self.load_pathloss_if_required()
+        return new_config
 
     def calibrated_downlink_rx_power(self, bts_config, rsrp):
         """ LTE simulation overrides this method so that it can convert from
diff --git a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
index 94e8901..6ea9589 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
@@ -44,6 +44,9 @@
 WAIT_FOR_EXISTING_FLASH_TO_FINISH_SEC = 360
 PROCESS_CHECK_WAIT_TIME_SEC = 30
 
+FUCHSIA_SDK_URL = "gs://fuchsia-sdk/development"
+FUCHSIA_RELEASE_TESTING_URL = "gs://fuchsia-release-testing/images"
+
 
 def get_private_key(ip_address, ssh_config):
     """Tries to load various ssh key types.
@@ -218,8 +221,6 @@
                          'fuchsia_devices.')
     if not fuchsia_device.build_number:
         fuchsia_device.build_number = 'LATEST'
-    if not fuchsia_device.server_path:
-        fuchsia_device.server_path = 'gs://fuchsia-sdk/development/'
     if (utils.is_valid_ipv4_address(fuchsia_device.orig_ip)
             or utils.is_valid_ipv6_address(fuchsia_device.orig_ip)):
         raise ValueError('The fuchsia_device ip must be the mDNS name to be '
@@ -227,38 +228,23 @@
 
     if not fuchsia_device.specific_image:
         file_download_needed = True
-        if 'LATEST' in fuchsia_device.build_number:
-            gsutil_process = job.run('gsutil ls %s' %
-                                     fuchsia_device.server_path).stdout
-            build_list = list(
-                # filter out builds that are not part of branches
-                filter(
-                    None,
-                    gsutil_process.replace(fuchsia_device.server_path,
-                                           '').replace('/', '').split('\n')))
-            if 'LATEST_F' in fuchsia_device.build_number:
-                build_number = fuchsia_device.build_number.split(
-                    'LATEST_F', 1)[1]
-                build_list = [
-                    x for x in build_list if x.startswith('%s.' % build_number)
-                ]
-            elif fuchsia_device.build_number == 'LATEST':
-                build_list = [x for x in build_list if '.' in x]
-            if build_list:
-                fuchsia_device.build_number = build_list[-2]
-            else:
-                raise FileNotFoundError(
-                    'No build(%s) on the found on %s.' %
-                    (fuchsia_device.build_number, fuchsia_device.server_path))
-        image_front_string = fuchsia_device.product_type
+        product_build = fuchsia_device.product_type
         if fuchsia_device.build_type:
-            image_front_string = '%s_%s' % (image_front_string,
-                                            fuchsia_device.build_type)
-        full_image_string = '%s.%s-release.tgz' % (image_front_string,
-                                                   fuchsia_device.board_type)
-        file_to_download = '%s%s/images/%s' % (fuchsia_device.server_path,
-                                               fuchsia_device.build_number,
-                                               full_image_string)
+            product_build = '{}_{}'.format(product_build,
+                                           fuchsia_device.build_type)
+        if 'LATEST' in fuchsia_device.build_number:
+            sdk_version = 'sdk'
+            if 'LATEST_F' in fuchsia_device.build_number:
+                f_branch = fuchsia_device.build_number.split('LATEST_F', 1)[1]
+                sdk_version = 'f{}_sdk'.format(f_branch)
+            file_to_download = '{}/{}-{}.{}-release.tgz'.format(
+                FUCHSIA_RELEASE_TESTING_URL, sdk_version, product_build,
+                fuchsia_device.board_type)
+        else:
+            # Must be a fully qualified build number (e.g. 5.20210721.4.1215)
+            file_to_download = '{}/{}/images/{}.{}-release.tgz'.format(
+                FUCHSIA_SDK_URL, fuchsia_device.build_number, product_build,
+                fuchsia_device.board_type)
     elif 'gs://' in fuchsia_device.specific_image:
         file_download_needed = True
         file_to_download = fuchsia_device.specific_image
@@ -267,6 +253,7 @@
         file_to_download = fuchsia_device.specific_image
     else:
         raise ValueError('A suitable build could not be found.')
+
     tmp_path = '/tmp/%s_%s' % (str(int(
         time.time() * 10000)), fuchsia_device.board_type)
     os.mkdir(tmp_path)
diff --git a/acts/framework/acts/controllers/iperf_client.py b/acts/framework/acts/controllers/iperf_client.py
index b31d54f..6c889b1 100644
--- a/acts/framework/acts/controllers/iperf_client.py
+++ b/acts/framework/acts/controllers/iperf_client.py
@@ -36,6 +36,7 @@
 from acts.event.event import TestClassEndEvent
 from acts.libs.proc import job
 from paramiko.buffered_pipe import PipeTimeout
+from paramiko.ssh_exception import SSHException
 MOBLY_CONTROLLER_CONFIG_NAME = 'IPerfClient'
 ACTS_CONTROLLER_REFERENCE_NAME = 'iperf_clients'
 
@@ -243,8 +244,10 @@
         except socket.timeout:
             raise TimeoutError('Socket timeout. Timed out waiting for iperf '
                                'client to finish.')
-        except Exception as e:
-            logging.exception('iperf run failed.')
+        except SSHException as err:
+            raise ConnectionError('SSH connection failed: {}'.format(err))
+        except Exception as err:
+            logging.exception('iperf run failed: {}'.format(err))
 
         return full_out_path
 
diff --git a/acts/framework/acts/controllers/openwrt_ap.py b/acts/framework/acts/controllers/openwrt_ap.py
index bc299ed..22221ef 100644
--- a/acts/framework/acts/controllers/openwrt_ap.py
+++ b/acts/framework/acts/controllers/openwrt_ap.py
@@ -1,7 +1,7 @@
 """Controller for Open WRT access point."""
 
+import re
 import time
-
 from acts import logger
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.openwrt_lib import network_settings
@@ -14,6 +14,7 @@
 MOBLY_CONTROLLER_CONFIG_NAME = "OpenWrtAP"
 ACTS_CONTROLLER_REFERENCE_NAME = "access_points"
 OPEN_SECURITY = "none"
+PSK1_SECURITY = 'psk'
 PSK_SECURITY = "psk2"
 WEP_SECURITY = "wep"
 ENT_SECURITY = "wpa2"
@@ -23,6 +24,7 @@
 PMF_ENABLED = 2
 WIFI_2G = "wifi2g"
 WIFI_5G = "wifi5g"
+WAIT_TIME = 20
 DEFAULT_RADIOS = ("radio0", "radio1")
 
 
@@ -150,12 +152,24 @@
   def start_ap(self):
     """Starts the AP with the settings in /etc/config/wireless."""
     self.ssh.run("wifi up")
-    time.sleep(9)  # wait for sometime for AP to come up
+    curr_time = time.time()
+    while time.time() < curr_time + WAIT_TIME:
+      if self.get_wifi_status():
+        return
+      time.sleep(3)
+    if not self.get_wifi_status():
+      raise ValueError("Failed to turn on WiFi on the AP.")
 
   def stop_ap(self):
     """Stops the AP."""
     self.ssh.run("wifi down")
-    time.sleep(9)  # wait for sometime for AP to go down
+    curr_time = time.time()
+    while time.time() < curr_time + WAIT_TIME:
+      if not self.get_wifi_status():
+        return
+      time.sleep(3)
+    if self.get_wifi_status():
+      raise ValueError("Failed to turn off WiFi on the AP.")
 
   def get_bssids_for_wifi_networks(self):
     """Get BSSIDs for wifi networks configured.
@@ -174,6 +188,21 @@
           bssid_map["2g"][ssid] = self.get_bssid(ifname)
     return bssid_map
 
+  def get_wifi_status(self):
+    """Check if radios are up for both 2G and 5G bands.
+
+    Returns:
+      True if both radios are up. False if not.
+    """
+    radios = ["radio0", "radio1"]
+    status = True
+    for radio in radios:
+      str_output = self.ssh.run("wifi status %s" % radio).stdout
+      wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""),
+                              Loader=yaml.FullLoader)
+      status = wifi_status[radio]["up"] and status
+    return status
+
   def get_ifnames_for_ssids(self, radio):
     """Get interfaces for wifi networks.
 
@@ -209,6 +238,77 @@
     mac_addr = ifconfig.split("\n")[0].split()[-1]
     return mac_addr
 
+  def set_wpa_encryption(self, encryption):
+    """Set different encryptions to wpa or wpa2.
+
+    Args:
+      encryption: ccmp, tkip, or ccmp+tkip.
+    """
+    str_output = self.ssh.run("wifi status").stdout
+    wifi_status = yaml.load(str_output.replace("\t", "").replace("\n", ""),
+                            Loader=yaml.FullLoader)
+
+    # Counting how many interface are enabled.
+    total_interface = 0
+    for radio in ["radio0", "radio1"]:
+      num_interface = len(wifi_status[radio]['interfaces'])
+      total_interface += num_interface
+
+    # Iterates every interface to get and set wpa encryption.
+    default_extra_interface = 2
+    for i in range(total_interface + default_extra_interface):
+      origin_encryption = self.ssh.run(
+          'uci get wireless.@wifi-iface[{}].encryption'.format(i)).stdout
+      origin_psk_pattern = re.match(r'psk\b', origin_encryption)
+      target_psk_pattern = re.match(r'psk\b', encryption)
+      origin_psk2_pattern = re.match(r'psk2\b', origin_encryption)
+      target_psk2_pattern = re.match(r'psk2\b', encryption)
+
+      if origin_psk_pattern == target_psk_pattern:
+        self.ssh.run(
+            'uci set wireless.@wifi-iface[{}].encryption={}'.format(
+                i, encryption))
+
+      if origin_psk2_pattern == target_psk2_pattern:
+        self.ssh.run(
+            'uci set wireless.@wifi-iface[{}].encryption={}'.format(
+                i, encryption))
+
+    self.ssh.run("uci commit wireless")
+    self.ssh.run("wifi")
+
+  def set_password(self, pwd_5g=None, pwd_2g=None):
+    """Set password for individual interface.
+
+    Args:
+        pwd_5g: 8 ~ 63 chars, ascii letters and digits password for 5g network.
+        pwd_2g: 8 ~ 63 chars, ascii letters and digits password for 2g network.
+    """
+    if pwd_5g:
+      if len(pwd_5g) < 8 or len(pwd_5g) > 63:
+        self.log.error("Password must be 8~63 characters long")
+      # Only accept ascii letters and digits
+      elif not re.match("^[A-Za-z0-9]*$", pwd_5g):
+        self.log.error("Password must only contains ascii letters and digits")
+      else:
+        self.ssh.run(
+            'uci set wireless.@wifi-iface[{}].key={}'.format(3, pwd_5g))
+        self.log.info("Set 5G password to :{}".format(pwd_2g))
+
+    if pwd_2g:
+      if len(pwd_2g) < 8 or len(pwd_2g) > 63:
+        self.log.error("Password must be 8~63 characters long")
+      # Only accept ascii letters and digits
+      elif not re.match("^[A-Za-z0-9]*$", pwd_2g):
+        self.log.error("Password must only contains ascii letters and digits")
+      else:
+        self.ssh.run(
+            'uci set wireless.@wifi-iface[{}].key={}'.format(2, pwd_2g))
+        self.log.info("Set 2G password to :{}".format(pwd_2g))
+
+    self.ssh.run("uci commit wireless")
+    self.ssh.run("wifi")
+
   def generate_wireless_configs(self, wifi_configs):
     """Generate wireless configs to configure.
 
@@ -236,6 +336,14 @@
                                              hostapd_constants.BAND_2G,
                                              password=config["password"],
                                              hidden=config["hiddenSSID"]))
+        elif config["security"] == PSK1_SECURITY:
+          wireless_configs.append(
+              wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g),
+                                             config["SSID"],
+                                             config["security"],
+                                             hostapd_constants.BAND_2G,
+                                             password=config["password"],
+                                             hidden=config["hiddenSSID"]))
         elif config["security"] == WEP_SECURITY:
           wireless_configs.append(
               wireless_config.WirelessConfig("%s%s" % (WIFI_2G, num_2g),
@@ -290,6 +398,14 @@
                                              hostapd_constants.BAND_5G,
                                              password=config["password"],
                                              hidden=config["hiddenSSID"]))
+        elif config["security"] == PSK1_SECURITY:
+          wireless_configs.append(
+              wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g),
+                                             config["SSID"],
+                                             config["security"],
+                                             hostapd_constants.BAND_5G,
+                                             password=config["password"],
+                                             hidden=config["hiddenSSID"]))
         elif config["security"] == WEP_SECURITY:
           wireless_configs.append(
               wireless_config.WirelessConfig("%s%s" % (WIFI_5G, num_5g),
diff --git a/acts/framework/acts/controllers/openwrt_lib/OWNERS b/acts/framework/acts/controllers/openwrt_lib/OWNERS
index 1840d87..6ddb5ea 100644
--- a/acts/framework/acts/controllers/openwrt_lib/OWNERS
+++ b/acts/framework/acts/controllers/openwrt_lib/OWNERS
@@ -1,3 +1,4 @@
 jerrypcchen@google.com
 gmoturu@google.com
 martschneider@google.com
+sishichen@google.com
diff --git a/acts/framework/acts/controllers/openwrt_lib/network_settings.py b/acts/framework/acts/controllers/openwrt_lib/network_settings.py
index efbd590..ca1b212 100644
--- a/acts/framework/acts/controllers/openwrt_lib/network_settings.py
+++ b/acts/framework/acts/controllers/openwrt_lib/network_settings.py
@@ -28,11 +28,12 @@
 SERVICE_IPSEC = "ipsec"
 SERVICE_XL2TPD = "xl2tpd"
 SERVICE_ODHCPD = "odhcpd"
-SERVICE_NODOGSPLASH = "nodogsplash"
+SERVICE_OPENNDS = "opennds"
+SERVICE_UHTTPD = "uhttpd"
 PPTP_PACKAGE = "pptpd kmod-nf-nathelper-extra"
 L2TP_PACKAGE = "strongswan-full openssl-util xl2tpd"
 NAT6_PACKAGE = "ip6tables kmod-ipt-nat6"
-CAPTIVE_PORTAL_PACKAGE = "nodogsplash"
+CAPTIVE_PORTAL_PACKAGE = "opennds php7-cli php7-mod-openssl php7-cgi php7"
 MDNS_PACKAGE = "avahi-utils avahi-daemon-service-http avahi-daemon-service-ssh libavahi-client avahi-dbus-daemon"
 STUNNEL_CONFIG_PATH = "/etc/stunnel/DoTServer.conf"
 HISTORY_CONFIG_PATH = "/etc/dirty_configs"
@@ -188,13 +189,28 @@
         return False
 
     def path_exists(self, abs_path):
-        """Check if dir exist on OpenWrt."""
+        """Check if dir exist on OpenWrt.
+
+        Args:
+            abs_path: absolutely path for create folder.
+        """
         try:
             self.ssh.run("ls %s" % abs_path)
         except:
             return False
         return True
 
+    def create_folder(self, abs_path):
+        """If dir not exist, create it.
+
+        Args:
+            abs_path: absolutely path for create folder.
+        """
+        if not self.path_exists(abs_path):
+            self.ssh.run("mkdir %s" % abs_path)
+        else:
+            self.log.info("%s already existed." %abs_path)
+
     def count(self, config, key):
         """Count in uci config.
 
@@ -849,6 +865,7 @@
             tcpdump_file_name: tcpdump file name on OpenWrt.
             pid: tcpdump process id.
         """
+        self.package_install("tcpdump")
         if not self.path_exists(TCPDUMP_DIR):
             self.ssh.run("mkdir %s" % TCPDUMP_DIR)
         tcpdump_file_name = "openwrt_%s_%s.pcap" % (test_name,
@@ -920,15 +937,54 @@
         self.service_manager.need_restart(SERVICE_FIREWALL)
         self.commit_changes()
 
-    def setup_captive_portal(self):
+    def setup_captive_portal(self, fas_fdqn,fas_port=2080):
+        """Create captive portal with Forwarding Authentication Service.
+
+        Args:
+             fas_fdqn: String for captive portal page's fdqn add to local dns server.
+             fas_port: Port for captive portal page.
+        """
         self.package_install(CAPTIVE_PORTAL_PACKAGE)
-        self.config.add("setup_captive_portal")
-        self.service_manager.need_restart(SERVICE_NODOGSPLASH)
+        self.config.add("setup_captive_portal %s" % fas_port)
+        self.ssh.run("uci set opennds.@opennds[0].fas_secure_enabled=2")
+        self.ssh.run("uci set opennds.@opennds[0].gatewayport=2050")
+        self.ssh.run("uci set opennds.@opennds[0].fasport=%s" % fas_port)
+        self.ssh.run("uci set opennds.@opennds[0].fasremotefqdn=%s" % fas_fdqn)
+        self.ssh.run("uci set opennds.@opennds[0].faspath=\"/nds/fas-aes.php\"")
+        self.ssh.run("uci set opennds.@opennds[0].faskey=1234567890")
+        self.service_manager.need_restart(SERVICE_OPENNDS)
+        # Config uhttpd
+        self.ssh.run("uci set uhttpd.main.interpreter=.php=/usr/bin/php-cgi")
+        self.ssh.run("uci add_list uhttpd.main.listen_http=0.0.0.0:%s" % fas_port)
+        self.ssh.run("uci add_list uhttpd.main.listen_http=[::]:%s" % fas_port)
+        self.service_manager.need_restart(SERVICE_UHTTPD)
+        # cp fas-aes.php
+        self.create_folder("/www/nds/")
+        self.ssh.run("cp /etc/opennds/fas-aes.php /www/nds")
+        # Add fdqn
+        self.add_resource_record(fas_fdqn, LOCALHOST)
         self.commit_changes()
 
-    def remove_cpative_portal(self):
+    def remove_cpative_portal(self, fas_port=2080):
+        """Remove captive portal.
+
+        Args:
+             fas_port: Port for captive portal page.
+        """
+        # Remove package
         self.package_remove(CAPTIVE_PORTAL_PACKAGE)
-        self.config.discard("setup_captive_portal")
+        # Clean up config
+        self.ssh.run("rm /etc/config/opennds")
+        # Remove fdqn
+        self.clear_resource_record()
+        # Restore uhttpd
+        self.ssh.run("uci del uhttpd.main.interpreter")
+        self.ssh.run("uci del_list uhttpd.main.listen_http=\'0.0.0.0:%s\'" % fas_port)
+        self.ssh.run("uci del_list uhttpd.main.listen_http=\'[::]:%s\'" % fas_port)
+        self.service_manager.need_restart(SERVICE_UHTTPD)
+        # Clean web root
+        self.ssh.run("rm -r /www/nds")
+        self.config.discard("setup_captive_portal %s" % fas_port)
         self.commit_changes()
 
 
diff --git a/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py b/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py
new file mode 100644
index 0000000..5848b5b
--- /dev/null
+++ b/acts/framework/acts/controllers/openwrt_lib/openwrt_constants.py
@@ -0,0 +1,26 @@
+#!/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.
+
+class OpenWrtWifiSecurity:
+  # Used by OpenWrt AP
+  WPA_PSK_DEFAULT = "psk"
+  WPA_PSK_CCMP = "psk+ccmp"
+  WPA_PSK_TKIP = "psk+tkip"
+  WPA_PSK_TKIP_AND_CCMP = "psk+tkip+ccmp"
+  WPA2_PSK_DEFAULT = "psk2"
+  WPA2_PSK_CCMP = "psk2+ccmp"
+  WPA2_PSK_TKIP = "psk2+tkip"
+  WPA2_PSK_TKIP_AND_CCMP = "psk2+tkip+ccmp"
\ No newline at end of file
diff --git a/acts/framework/acts/controllers/openwrt_lib/wireless_settings_applier.py b/acts/framework/acts/controllers/openwrt_lib/wireless_settings_applier.py
index 6822ae1..ec0bbf1 100644
--- a/acts/framework/acts/controllers/openwrt_lib/wireless_settings_applier.py
+++ b/acts/framework/acts/controllers/openwrt_lib/wireless_settings_applier.py
@@ -9,6 +9,7 @@
 
 LEASE_FILE = "/tmp/dhcp.leases"
 OPEN_SECURITY = "none"
+PSK1_SECURITY = "psk"
 PSK_SECURITY = "psk2"
 WEP_SECURITY = "wep"
 ENT_SECURITY = "wpa2"
@@ -53,6 +54,14 @@
     # set channels for 2G and 5G bands
     self.ssh.run("uci set wireless.radio1.channel='%s'" % self.channel_2g)
     self.ssh.run("uci set wireless.radio0.channel='%s'" % self.channel_5g)
+    if self.channel_5g == 165:
+      self.ssh.run("uci set wireless.radio0.htmode='VHT20'")
+    elif self.channel_5g == 132 or self.channel_5g == 136:
+      self.ssh.run("iw reg set ZA")
+      self.ssh.run("uci set wireless.radio0.htmode='VHT40'")
+
+    if self.channel_2g == 13:
+      self.ssh.run("iw reg set AU")
 
     # disable default OpenWrt SSID
     self.ssh.run("uci set wireless.default_radio1.disabled='%s'" %
@@ -98,7 +107,8 @@
                    (config.name, config.ssid))
       self.ssh.run("uci set wireless.%s.encryption='%s'" %
                    (config.name, config.security))
-      if config.security == PSK_SECURITY or config.security == SAE_SECURITY:
+      if config.security == PSK_SECURITY or config.security == SAE_SECURITY\
+              or config.security == PSK1_SECURITY:
         self.ssh.run("uci set wireless.%s.key='%s'" %
                      (config.name, config.password))
       elif config.security == WEP_SECURITY:
@@ -128,6 +138,8 @@
     self.ssh.run("wifi down")
     self.ssh.run("rm -f /etc/config/wireless")
     self.ssh.run("wifi config")
+    if self.channel_5g == 132:
+      self.ssh.run("iw reg set US")
     self.ssh.run("cp %s.tmp %s" % (LEASE_FILE, LEASE_FILE))
     self.service_manager.restart(SERVICE_DNSMASQ)
     time.sleep(9)
diff --git a/acts/framework/acts/controllers/power_metrics.py b/acts/framework/acts/controllers/power_metrics.py
index f68edcc..f4d9edd 100644
--- a/acts/framework/acts/controllers/power_metrics.py
+++ b/acts/framework/acts/controllers/power_metrics.py
@@ -61,129 +61,6 @@
 }
 
 
-class AbsoluteThresholds(object):
-    """Class to represent thresholds in absolute (non-relative) values.
-
-    Attributes:
-        lower: Lower limit of the threshold represented by a measurement.
-        upper: Upper limit of the threshold represented by a measurement.
-        unit_type: Type of the unit (current, power, etc).
-        unit: The  unit for this threshold (W, mW, uW).
-    """
-
-    def __init__(self, lower, upper, unit_type, unit):
-        self.unit_type = unit_type
-        self.unit = unit
-        self.lower = Metric(lower, unit_type, unit)
-        self.upper = Metric(upper, unit_type, unit)
-
-    @staticmethod
-    def from_percentual_deviation(expected, percentage, unit_type, unit):
-        """Creates an AbsoluteThresholds object from an expected value and its
-        allowed percentual deviation (also in terms of the expected value).
-
-        For example, if the expected value is 20 and the deviation 25%, this
-        would imply that the absolute deviation is 20 * 0.25 = 5 and therefore
-        the absolute threshold would be from (20-5, 20+5) or (15, 25).
-
-        Args:
-            expected: Central value from which the deviation will be estimated.
-            percentage: Percentage of allowed deviation, the percentage itself
-            is in terms of the expected value.
-            unit_type: Type of the unit (current, power, etc).
-            unit: Unit for this threshold (W, mW, uW).
-        """
-        return AbsoluteThresholds(expected * (1 - percentage / 100),
-                                  expected * (1 + percentage / 100),
-                                  unit_type,
-                                  unit)
-
-    @staticmethod
-    def from_threshold_conf(thresholds_conf):
-        """Creates a AbsoluteThresholds object from a ConfigWrapper describing
-        a threshold (either absolute or percentual).
-
-        Args:
-            thresholds_conf: ConfigWrapper object that describes a threshold.
-        Returns:
-            AbsolutesThresholds object.
-        Raises:
-            ValueError if configuration is incorrect or incomplete.
-        """
-        if 'unit_type' not in thresholds_conf:
-            raise ValueError(
-                'A threshold config must contain a unit_type. %s is incorrect'
-                % str(thresholds_conf))
-
-        if 'unit' not in thresholds_conf:
-            raise ValueError(
-                'A threshold config must contain a unit. %s is incorrect'
-                % str(thresholds_conf))
-
-        unit_type = thresholds_conf['unit_type']
-        unit = thresholds_conf['unit']
-
-        is_relative = (
-            'expected_value' in thresholds_conf and
-            'percent_deviation' in thresholds_conf)
-
-        is_almost_relative = (
-            'expected_value' in thresholds_conf or
-            'percent_deviation' in thresholds_conf)
-
-        is_absolute = ('lower_limit' in thresholds_conf or
-                       'upper_limit' in thresholds_conf)
-
-        if is_absolute and is_almost_relative:
-            raise ValueError(
-                'Thresholds can either be absolute (with lower_limit and'
-                'upper_limit defined) or by percentual deviation (with'
-                'expected_value and percent_deviation defined), but never'
-                'a mixture of both. %s is incorrect'
-                % str(thresholds_conf))
-
-        if is_almost_relative and not is_relative:
-            if 'expected_value' not in thresholds_conf:
-                raise ValueError(
-                    'Incomplete definition of a threshold by percentual '
-                    'deviation. percent_deviation given, but missing '
-                    'expected_value. %s is incorrect'
-                    % str(thresholds_conf))
-
-            if 'percent_deviation' not in thresholds_conf:
-                raise ValueError(
-                    'Incomplete definition of a threshold by percentual '
-                    'deviation. expected_value given, but missing '
-                    'percent_deviation. %s is incorrect'
-                    % str(thresholds_conf))
-
-        if not is_absolute and not is_relative:
-            raise ValueError(
-                'Thresholds must be either absolute (with lower_limit and'
-                'upper_limit defined) or defined by percentual deviation (with'
-                'expected_value and percent_deviation defined). %s is incorrect'
-                % str(thresholds_conf))
-
-        if is_relative:
-            expected = thresholds_conf.get_numeric('expected_value')
-            percent = thresholds_conf.get_numeric('percent_deviation')
-
-            thresholds = (
-                AbsoluteThresholds.from_percentual_deviation(
-                    expected,
-                    percent,
-                    unit_type, unit))
-
-        else:
-            lower_value = thresholds_conf.get_numeric('lower_limit',
-                                                      float('-inf'))
-            upper_value = thresholds_conf.get_numeric('upper_limit',
-                                                      float('inf'))
-            thresholds = AbsoluteThresholds(lower_value, upper_value, unit_type,
-                                            unit)
-        return thresholds
-
-
 class Metric(object):
     """Base class for describing power measurement values. Each object contains
     an value and a unit. Enables some basic arithmetic operations with other
diff --git a/acts/framework/acts/controllers/spectracom_lib/gsg6.py b/acts/framework/acts/controllers/spectracom_lib/gsg6.py
index 6b96456..a1c30cc 100644
--- a/acts/framework/acts/controllers/spectracom_lib/gsg6.py
+++ b/acts/framework/acts/controllers/spectracom_lib/gsg6.py
@@ -118,3 +118,13 @@
         infmsg = 'Set GSG-6 transmit power to "{}"'.format(
             round(power_level, 1))
         self._logger.debug(infmsg)
+
+    def get_nmealog(self):
+        """Get GSG6 NMEA data.
+
+        Returns:
+            GSG6's NMEA data
+        """
+        nmea_data = self._query('SOUR:SCEN:LOG?')
+
+        return nmea_data
diff --git a/acts/framework/acts/controllers/tigertail.py b/acts/framework/acts/controllers/tigertail.py
new file mode 100644
index 0000000..3a0ff6a
--- /dev/null
+++ b/acts/framework/acts/controllers/tigertail.py
@@ -0,0 +1,116 @@
+"""Module manager the required definitions for tigertail"""
+
+import logging
+import os
+import time
+
+from enum import Enum
+
+from acts.libs.proc import job
+
+MOBLY_CONTROLLER_CONFIG_NAME = "Tigertail"
+ACTS_CONTROLLER_REFERENCE_NAME = "tigertails"
+
+TIGERTAIL_SLEEP_TIME = 5
+
+def create(configs):
+    """Takes a list of Tigertail serial numbers and returns Tigertail Controllers.
+
+    Args:
+        configs: A list of serial numbers
+
+    Returns:
+        a list of Tigertail controllers
+
+    Raises:
+        ValueError if the configuration is not a list of serial number
+    """
+    tigertails = []
+    if isinstance(configs, list):
+        for serial_no in configs:
+            tigertail = Tigertail(serial_no)
+            tigertails.append(tigertail)
+    else:
+        raise ValueError('Invalid config for tigertail, should be a list of serial number')
+
+    return tigertails
+
+
+def destroy(tigertails):
+    pass
+
+def get_info(tigertails):
+    return [tigertail.get_info() for tigertail in tigertails]
+
+class TigertailError(Exception):
+    pass
+
+class TigertailState(Enum):
+    def __str__(self):
+        return str(self.value)
+    A = 'A'
+    B = 'B'
+    Off = 'off'
+
+class Tigertail(object):
+    def __init__(self, serial_number):
+        self.serial_number = serial_number
+        self.tigertool_bin = None
+
+    def setup(self, user_params):
+        """Links tigertool binary
+
+        This function needs to be:
+        Args:
+            user_params: User defined parameters. Expected field is:
+            {
+                // required, string or list of strings
+                tigertool: ['/path/to/tigertool.par']
+            }
+        """
+        tigertool_path = user_params['tigertool']
+        if tigertool_path is None:
+            self.tigertool_bin = None
+        elif isinstance(tigertool_path, str):
+            self.tigertool_bin = tigertool_path
+        elif isinstance(tigertool_path, list):
+            if len(tigertool_path) == 0:
+                self.tigertool_bin = None
+            else:
+                self.tigertool_bin = tigertool_path[0]
+
+        if self.tigertool_bin is None:
+            raise TigertailError('Tigertail binary not found')
+
+        logging.getLogger().debug(f'Setup {self.serial_number} with binary at {self.tigertool_bin}')
+
+    def turn_on_mux_A(self):
+        self._set_tigertail_state(TigertailState.A)
+
+    def turn_on_mux_B(self):
+        self._set_tigertail_state(TigertailState.B)
+
+    def turn_off(self):
+        self._set_tigertail_state(TigertailState.Off)
+
+    def get_info(self):
+        return {'tigertail_serial_no': self.serial_number}
+
+    def _set_tigertail_state(self, state: TigertailState):
+        """Sets state for tigertail, there are 3 possible states:
+            A  : enable port A
+            B  : enable port B
+            Off: turn off both ports
+        """
+        result = job.run([self.tigertool_bin,
+            '--serialno',
+            str(self.serial_number),
+            '--mux',
+            str(state)],
+            timeout=10)
+
+        if result.stderr != '':
+            raise TigertailError(result.stderr)
+
+        # Sleep time to let the device connected/disconnect to tigertail
+        time.sleep(TIGERTAIL_SLEEP_TIME)
diff --git a/acts/framework/acts/error.py b/acts/framework/acts/error.py
index 8d8fa5a..d1857d3 100644
--- a/acts/framework/acts/error.py
+++ b/acts/framework/acts/error.py
@@ -39,3 +39,4 @@
 
     FastbootError = 9000
     AdbError = 9001
+    AdbCommandError = 9002
diff --git a/acts/framework/acts/keys.py b/acts/framework/acts/keys.py
index ae89456..c4c4b44 100644
--- a/acts/framework/acts/keys.py
+++ b/acts/framework/acts/keys.py
@@ -41,6 +41,7 @@
     key_test_failure_tracebacks = 'test_failure_tracebacks'
     # Config names for controllers packaged in ACTS.
     key_android_device = 'AndroidDevice'
+    key_bits = 'Bits'
     key_bluetooth_pts_device = 'BluetoothPtsDevice'
     key_fuchsia_device = 'FuchsiaDevice'
     key_buds_device = 'BudsDevice'
@@ -58,6 +59,8 @@
     key_packet_capture = 'PacketCapture'
     key_pdu = 'PduDevice'
     key_openwrt_ap = 'OpenWrtAP'
+    key_tigertail = 'Tigertail'
+    key_asus_axe11000_ap = 'AsusAXE11000AP'
     # Internal keys, used internally, not exposed to user's config files.
     ikey_user_param = 'user_params'
     ikey_testbed_name = 'testbed_name'
@@ -65,6 +68,7 @@
     ikey_logpath = 'log_path'
     ikey_summary_writer = 'summary_writer'
     # module name of controllers packaged in ACTS.
+    m_key_bits = 'bits'
     m_key_monsoon = 'monsoon'
     m_key_android_device = 'android_device'
     m_key_fuchsia_device = 'fuchsia_device'
@@ -83,6 +87,8 @@
     m_key_packet_capture = 'packet_capture'
     m_key_pdu = 'pdu'
     m_key_openwrt_ap = 'openwrt_ap'
+    m_key_tigertail = 'tigertail'
+    m_key_asus_axe11000_ap = 'asus_axe11000_ap'
 
     # A list of keys whose values in configs should not be passed to test
     # classes without unpacking first.
@@ -91,6 +97,7 @@
     # Controller names packaged with ACTS.
     builtin_controller_names = [
         key_android_device,
+        key_bits,
         key_bluetooth_pts_device,
         key_fuchsia_device,
         key_buds_device,
@@ -108,6 +115,8 @@
         key_packet_capture,
         key_pdu,
         key_openwrt_ap,
+        key_tigertail,
+        key_asus_axe11000_ap,
     ]
 
     # Keys that are file or folder paths.
diff --git a/acts_tests/tests/google/__init__.py b/acts/framework/acts/libs/testtracker/__init__.py
similarity index 100%
rename from acts_tests/tests/google/__init__.py
rename to acts/framework/acts/libs/testtracker/__init__.py
diff --git a/acts_tests/tests/google/__init__.py b/acts/framework/acts/libs/testtracker/protos/__init__.py
similarity index 100%
copy from acts_tests/tests/google/__init__.py
copy to acts/framework/acts/libs/testtracker/protos/__init__.py
diff --git a/acts_tests/tests/google/__init__.py b/acts/framework/acts/libs/testtracker/protos/gen/__init__.py
similarity index 100%
copy from acts_tests/tests/google/__init__.py
copy to acts/framework/acts/libs/testtracker/protos/gen/__init__.py
diff --git a/acts/framework/acts/libs/testtracker/protos/gen/testtracker_result_pb2.py b/acts/framework/acts/libs/testtracker/protos/gen/testtracker_result_pb2.py
new file mode 100644
index 0000000..97f576b
--- /dev/null
+++ b/acts/framework/acts/libs/testtracker/protos/gen/testtracker_result_pb2.py
@@ -0,0 +1,275 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: testtracker_result.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='testtracker_result.proto',
+  package='',
+  syntax='proto2',
+  serialized_options=None,
+  serialized_pb=_b('\n\x18testtracker_result.proto\"\xa8\x01\n\x08Property\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0cstring_value\x18\x65 \x01(\tH\x00\x12\x13\n\tint_value\x18\x66 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18g \x01(\x01H\x00\x12\x14\n\njson_value\x18h \x01(\tH\x00\x12\x14\n\nbool_value\x18i \x01(\x08H\x00\x12\x14\n\nbyte_value\x18j \x01(\x0cH\x00\x42\x07\n\x05value\"\xcc\x02\n\x06Result\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04uuid\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0e\n\x06\x64\x65tail\x18\x04 \x01(\t\x12\x1b\n\x08property\x18\x05 \x03(\x0b\x32\t.Property\x12\x11\n\ttimestamp\x18\n \x01(\t\x12.\n\x06status\x18\r \x01(\x0e\x32\x0e.Result.Status:\x0eSTATUS_UNKNOWN\"\xa0\x01\n\x06Status\x12\x12\n\x0eSTATUS_UNKNOWN\x10\x00\x12\n\n\x06PASSED\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x12\t\n\x05\x45RROR\x10\x03\x12\x0f\n\x0bINTERRUPTED\x10\x04\x12\r\n\tCANCELLED\x10\x05\x12\x0c\n\x08\x46ILTERED\x10\x06\x12\x0b\n\x07SKIPPED\x10\x07\x12\x0e\n\nSUPPRESSED\x10\x08\x12\x0b\n\x07\x42LOCKED\x10\t\x12\x07\n\x03TBR\x10\x11')
+)
+
+
+
+_RESULT_STATUS = _descriptor.EnumDescriptor(
+  name='Status',
+  full_name='Result.Status',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='STATUS_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PASSED', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='FAILED', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='INTERRUPTED', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CANCELLED', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='FILTERED', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SKIPPED', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SUPPRESSED', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='BLOCKED', index=9, number=9,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TBR', index=10, number=17,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=372,
+  serialized_end=532,
+)
+_sym_db.RegisterEnumDescriptor(_RESULT_STATUS)
+
+
+_PROPERTY = _descriptor.Descriptor(
+  name='Property',
+  full_name='Property',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='Property.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='string_value', full_name='Property.string_value', index=1,
+      number=101, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='int_value', full_name='Property.int_value', index=2,
+      number=102, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='double_value', full_name='Property.double_value', index=3,
+      number=103, type=1, cpp_type=5, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='json_value', full_name='Property.json_value', index=4,
+      number=104, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='bool_value', full_name='Property.bool_value', index=5,
+      number=105, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='byte_value', full_name='Property.byte_value', index=6,
+      number=106, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='value', full_name='Property.value',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=29,
+  serialized_end=197,
+)
+
+
+_RESULT = _descriptor.Descriptor(
+  name='Result',
+  full_name='Result',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='Result.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='uuid', full_name='Result.uuid', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='description', full_name='Result.description', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='detail', full_name='Result.detail', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='property', full_name='Result.property', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='timestamp', full_name='Result.timestamp', index=5,
+      number=10, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='status', full_name='Result.status', index=6,
+      number=13, type=14, cpp_type=8, label=1,
+      has_default_value=True, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _RESULT_STATUS,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=200,
+  serialized_end=532,
+)
+
+_PROPERTY.oneofs_by_name['value'].fields.append(
+  _PROPERTY.fields_by_name['string_value'])
+_PROPERTY.fields_by_name['string_value'].containing_oneof = _PROPERTY.oneofs_by_name['value']
+_PROPERTY.oneofs_by_name['value'].fields.append(
+  _PROPERTY.fields_by_name['int_value'])
+_PROPERTY.fields_by_name['int_value'].containing_oneof = _PROPERTY.oneofs_by_name['value']
+_PROPERTY.oneofs_by_name['value'].fields.append(
+  _PROPERTY.fields_by_name['double_value'])
+_PROPERTY.fields_by_name['double_value'].containing_oneof = _PROPERTY.oneofs_by_name['value']
+_PROPERTY.oneofs_by_name['value'].fields.append(
+  _PROPERTY.fields_by_name['json_value'])
+_PROPERTY.fields_by_name['json_value'].containing_oneof = _PROPERTY.oneofs_by_name['value']
+_PROPERTY.oneofs_by_name['value'].fields.append(
+  _PROPERTY.fields_by_name['bool_value'])
+_PROPERTY.fields_by_name['bool_value'].containing_oneof = _PROPERTY.oneofs_by_name['value']
+_PROPERTY.oneofs_by_name['value'].fields.append(
+  _PROPERTY.fields_by_name['byte_value'])
+_PROPERTY.fields_by_name['byte_value'].containing_oneof = _PROPERTY.oneofs_by_name['value']
+_RESULT.fields_by_name['property'].message_type = _PROPERTY
+_RESULT.fields_by_name['status'].enum_type = _RESULT_STATUS
+_RESULT_STATUS.containing_type = _RESULT
+DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY
+DESCRIPTOR.message_types_by_name['Result'] = _RESULT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict(
+  DESCRIPTOR = _PROPERTY,
+  __module__ = 'testtracker_result_pb2'
+  # @@protoc_insertion_point(class_scope:Property)
+  ))
+_sym_db.RegisterMessage(Property)
+
+Result = _reflection.GeneratedProtocolMessageType('Result', (_message.Message,), dict(
+  DESCRIPTOR = _RESULT,
+  __module__ = 'testtracker_result_pb2'
+  # @@protoc_insertion_point(class_scope:Result)
+  ))
+_sym_db.RegisterMessage(Result)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/acts/framework/acts/libs/testtracker/protos/testtracker_result.proto b/acts/framework/acts/libs/testtracker/protos/testtracker_result.proto
new file mode 100644
index 0000000..cb6bd05
--- /dev/null
+++ b/acts/framework/acts/libs/testtracker/protos/testtracker_result.proto
@@ -0,0 +1,74 @@
+syntax = "proto2";
+
+// Modified form of proto used for TestTracker.
+
+// The Result is the base proto for describing Results that can be stored
+// for writing to disk or uploading to a system.
+
+// Example:
+// name: "Test Case"
+// uuid: "d9e80ae6-dfa9-4713-9cc6-26231ff58f4b"
+// description: "This is sample test case"
+// detail: "This is really long text blob (test tracker test case text)"
+// status: PASSED
+
+// Each Property a key/value pair with a type based on the oneof field.
+// The default should be a string.
+message Property {
+  optional string name = 1;
+  // Reserved <100 for use with concrete fields.
+  oneof value {
+    // String representation of the property.
+    string string_value = 101;
+    // Integer(always use int64) representation of the property.
+    int64 int_value = 102;
+    // Floating(always use double) point representation of the property.
+    double double_value = 103;
+    // The json_value is a special case of string - this just signals the caller
+    // that the value should be parsable via json.
+    string json_value = 104;
+    // Boolean representation of the property.
+    bool bool_value = 105;
+    // Byte representation of the property.
+    bytes byte_value = 106;
+  }
+}
+
+// A Result contains information about the each piece of the report.
+// Status enum's define the state of a particular result.
+// In a perfect world everything will be PASSED.
+//
+message Result {
+  enum Status {
+    STATUS_UNKNOWN = 0;
+    PASSED = 1;       // Completed and all operators are evaluated to True.
+    FAILED = 2;       // Completed and at least one operator has failed.
+    ERROR = 3;        // Completed but there was an error reported.
+                      // 'code' should be assigned with the exact status.
+    INTERRUPTED = 4;  // The operation was initiated but did not complete.
+                      // This could be caused by a timeout or
+                      // by a user requesting cancellation mid-way.
+    CANCELLED = 5;    // Same as interrupted but did not start.
+    FILTERED = 6;     // Scheduled but some precondition caused it to be
+                      // removed from schedule.
+    SKIPPED = 7;      // Scheduled but was administratively skipped.
+    SUPPRESSED = 8;   // Flaky and we don't want it be official.
+    BLOCKED = 9;      // Test blocked by other test(s).
+    TBR = 17;         // To be reviewed, test requires additional result
+                      // assessment.
+  }
+
+  // Name of the result in the tree.
+  optional string name = 1;
+  optional string uuid = 2;         // uuid
+  optional string description = 3;  // Text blob about the result.
+  optional string detail = 4;       // Detailed text about the result.
+  // Key/Value Property pairs.
+  // If duplicate keys are presented the last value is taken.
+  repeated Property property = 5;  // Key/Value Property pairs.
+  // Time in ISO UTC (http://en.wikipedia.org/wiki/ISO_8601).
+  // This is the start of the result.
+  optional string timestamp = 10;
+  // Status defines the current state of this result.
+  optional Status status = 13 [default = STATUS_UNKNOWN];
+}
\ No newline at end of file
diff --git a/acts/framework/acts/libs/testtracker/testtracker_results_writer.py b/acts/framework/acts/libs/testtracker/testtracker_results_writer.py
new file mode 100644
index 0000000..a1c4df0
--- /dev/null
+++ b/acts/framework/acts/libs/testtracker/testtracker_results_writer.py
@@ -0,0 +1,172 @@
+#!/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 datetime
+import os
+
+from acts.libs.proto.proto_utils import parse_proto_to_ascii
+from acts.libs.testtracker.protos.gen.testtracker_result_pb2 import Result
+from acts.records import TestResultEnums
+from acts.records import TestResultRecord
+
+from acts import signals
+
+KEY_DETAILS = 'details'
+KEY_EFFORT_NAME = 'effort_name'
+KEY_PROJECT_ID = 'project_id'
+KEY_TESTTRACKER_UUID = 'test_tracker_uuid'
+KEY_USER = 'user'
+KEY_UUID = 'uuid'
+
+TESTTRACKER_PATH = 'test_tracker_results/test_effort_name=%s/test_case_uuid=%s'
+RESULT_FILE_NAME = 'result.pb.txt'
+
+_TEST_RESULT_TO_STATUS_MAP = {
+    TestResultEnums.TEST_RESULT_PASS: Result.PASSED,
+    TestResultEnums.TEST_RESULT_FAIL: Result.FAILED,
+    TestResultEnums.TEST_RESULT_SKIP: Result.SKIPPED,
+    TestResultEnums.TEST_RESULT_ERROR: Result.ERROR
+}
+
+
+class TestTrackerError(Exception):
+    """Exception class for errors raised within TestTrackerResultsWriter"""
+    pass
+
+
+class TestTrackerResultsWriter(object):
+    """Takes a test record, converts it to a TestTracker result proto, and
+    writes it to the log directory. In automation, these protos will
+    automatically be read from Sponge and uploaded to TestTracker.
+    """
+    def __init__(self, log_path, properties):
+        """Creates a TestTrackerResultsWriter
+
+        Args:
+            log_path: Base log path to store TestTracker results. Must be within
+                the ACTS directory.
+            properties: dict representing key-value pairs to be uploaded as
+                TestTracker properties.
+        """
+        self._log_path = log_path
+        self._properties = properties
+        self._validate_properties()
+
+    def write_results(self, record):
+        """Create a Result proto from test record, then write it to a file.
+
+        Args:
+            record: An acts.records.TestResultRecord object
+        """
+        proto = self._create_result_proto(record)
+        proto_dir = self._create_results_dir(proto.uuid)
+        with open(os.path.join(proto_dir, RESULT_FILE_NAME), mode='w') as f:
+            f.write(parse_proto_to_ascii(proto))
+
+    def write_results_from_test_signal(self, signal, begin_time=None):
+        """Create a Result proto from a test signal, then write it to a file.
+
+        Args:
+            signal: An acts.signals.TestSignal object
+            begin_time: Optional. Sets the begin_time of the test record.
+        """
+        record = TestResultRecord('')
+        record.begin_time = begin_time
+        if not record.begin_time:
+            record.test_begin()
+        if isinstance(signal, signals.TestPass):
+            record.test_pass(signal)
+        elif isinstance(signal, signals.TestFail):
+            record.test_fail(signal)
+        elif isinstance(signal, signals.TestSkip):
+            record.test_skip(signal)
+        else:
+            record.test_error(signal)
+        self.write_results(record)
+
+    def _validate_properties(self):
+        """Checks that the required properties are set
+
+        Raises:
+            TestTrackerError if one or more required properties is absent
+        """
+        required_props = [KEY_USER, KEY_PROJECT_ID, KEY_EFFORT_NAME]
+        missing_props = [p for p in required_props if p not in self._properties]
+        if missing_props:
+            raise TestTrackerError(
+                'Missing the following required properties for TestTracker: %s'
+                % missing_props)
+
+    @staticmethod
+    def _add_property(result_proto, name, value):
+        """Adds a Property to a given Result proto
+
+        Args:
+            result_proto: Result proto to modify
+            name: Property name
+            value: Property value
+        """
+        new_prop = result_proto.property.add()
+        new_prop.name = name
+        if isinstance(value, bool):
+            new_prop.bool_value = value
+        elif isinstance(value, int):
+            new_prop.int_value = value
+        elif isinstance(value, float):
+            new_prop.double_value = value
+        else:
+            new_prop.string_value = str(value)
+
+    def _create_result_proto(self, record):
+        """Create a Result proto object from test record. Fills in uuid, status,
+        and properties with info gathered from the test record.
+
+        Args:
+            record: An acts.records.TestResultRecord object
+
+        Returns: Result proto, or None if record is invalid
+        """
+        uuid = record.extras[KEY_TESTTRACKER_UUID]
+        result = Result()
+        result.uuid = uuid
+        result.status = _TEST_RESULT_TO_STATUS_MAP[record.result]
+        result.timestamp = (
+            datetime.datetime.fromtimestamp(
+                record.begin_time / 1000, datetime.timezone.utc)
+            .isoformat(timespec='milliseconds')
+            .replace('+00:00', 'Z'))
+
+        self._add_property(result, KEY_UUID, uuid)
+        if record.details:
+            self._add_property(result, KEY_DETAILS, record.details)
+
+        for key, value in self._properties.items():
+            self._add_property(result, key, value)
+
+        return result
+
+    def _create_results_dir(self, uuid):
+        """Creates the TestTracker directory given the test uuid
+
+        Args:
+            uuid: The TestTracker uuid of the test
+
+        Returns: Path to the created directory.
+        """
+        dir_path = os.path.join(self._log_path, TESTTRACKER_PATH % (
+            self._properties[KEY_EFFORT_NAME], uuid))
+        os.makedirs(dir_path, exist_ok=True)
+        return dir_path
diff --git a/acts/framework/acts/test_decorators.py b/acts/framework/acts/test_decorators.py
index 8eea7c8..9c504fc 100644
--- a/acts/framework/acts/test_decorators.py
+++ b/acts/framework/acts/test_decorators.py
@@ -14,6 +14,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import logging
+
+from acts.libs.testtracker.testtracker_results_writer import KEY_EFFORT_NAME
+from acts.libs.testtracker.testtracker_results_writer import TestTrackerError
+from acts.libs.testtracker.testtracker_results_writer import TestTrackerResultsWriter
+from mobly.base_test import BaseTestClass
+
 from acts import signals
 
 
@@ -136,10 +143,13 @@
         extra_environment_info: Extra info about the test tracker environment.
         predicate: A func that if false when called will ignore this info.
     """
-    return test_info(
-        test_tracker_uuid=uuid,
-        test_tracker_environment_info=extra_environment_info,
-        predicate=predicate)
+
+    def test_tracker_info_decorator(func):
+        keyed_info = dict(test_tracker_uuid=uuid,
+                          test_tracker_environment_info=extra_environment_info)
+        return _TestTrackerInfoDecoratorFunc(func, predicate, keyed_info)
+
+    return test_tracker_info_decorator
 
 
 class _TestInfoDecoratorFunc(object):
@@ -167,6 +177,13 @@
         When called runs the underlying func and then attaches test info
         to a signal.
         """
+        new_signal = self._get_signal_from_func_call(*args, **kwargs)
+        raise new_signal
+
+    def _get_signal_from_func_call(self, *args, **kwargs):
+        """Calls the underlying func, then attaches test info to the resulting
+        signal and raises the signal.
+        """
         cause = None
         try:
             result = self.func(*args, **kwargs)
@@ -240,6 +257,68 @@
         return extras
 
 
+class _TestTrackerInfoDecoratorFunc(_TestInfoDecoratorFunc):
+    """
+    Expands on _TestInfoDecoratorFunc by writing gathered test info to a
+    TestTracker proto file
+    """
+
+    def __call__(self, *args, **kwargs):
+        """
+        When called runs the underlying func and then attaches test info
+        to a signal. It then writes the result from the signal to a TestTracker
+        Result proto file.
+        """
+        try:
+            self._get_signal_from_func_call(*args, **kwargs)
+        except signals.TestSignal as new_signal:
+            if not args or not isinstance(args[0], BaseTestClass):
+                logging.warning('The decorated object must be an instance of'
+                                'an ACTS/Mobly test class.')
+            else:
+                self._write_to_testtracker(args[0], new_signal)
+            raise new_signal
+
+    def _write_to_testtracker(self, test_instance, signal):
+        """Write test result from given signal to a TestTracker Result proto
+        file.
+
+        Due to infra contraints on nested structures in userparams, this
+        expects the test_instance to have user_params defined as follows:
+
+            testtracker_properties: A comma-delimited list of
+                'prop_name=<userparam_name>'
+            <userparam_name>: testtracker property value.
+        """
+        tt_prop_to_param_names = test_instance.user_params.get(
+            'testtracker_properties')
+
+        if not tt_prop_to_param_names:
+            return
+
+        tt_prop_to_param_names = tt_prop_to_param_names.split(',')
+
+        testtracker_properties = {}
+        for entry in tt_prop_to_param_names:
+            prop_name, param_name = entry.split('=')
+            if param_name in test_instance.user_params:
+                testtracker_properties[prop_name] = (
+                    test_instance.user_params[param_name])
+
+        if (hasattr(test_instance, 'android_devices') and
+                KEY_EFFORT_NAME not in testtracker_properties):
+            testtracker_properties[KEY_EFFORT_NAME] = (
+                test_instance.android_devices[0].build_info['build_id'])
+
+        try:
+            writer = TestTrackerResultsWriter(
+                test_instance.log_path, testtracker_properties)
+            writer.write_results_from_test_signal(
+                signal, test_instance.begin_time)
+        except TestTrackerError:
+            test_instance.log.exception('TestTracker Error')
+
+
 class _TestInfoBinding(object):
     """
     When Python creates an instance of an object it creates a binding object
diff --git a/acts/framework/acts/utils.py b/acts/framework/acts/utils.py
index aec9ff2..58351a8 100755
--- a/acts/framework/acts/utils.py
+++ b/acts/framework/acts/utils.py
@@ -1411,7 +1411,7 @@
 
     Returns:
         A list of dictionaries of the the various IP addresses:
-            ipv4_private_local_addresses: Any 192.168, 172.16, or 10
+            ipv4_private_local_addresses: Any 192.168, 172.16, 10, or 169.254
                 addresses
             ipv4_public_addresses: Any IPv4 public addresses
             ipv6_link_local_addresses: Any fe80:: addresses
diff --git a/acts/framework/tests/acts_test_decorators_test.py b/acts/framework/tests/acts_test_decorators_test.py
index 8f2a632..58455d3 100755
--- a/acts/framework/tests/acts_test_decorators_test.py
+++ b/acts/framework/tests/acts_test_decorators_test.py
@@ -12,6 +12,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+import mock
 import shutil
 import tempfile
 import unittest
@@ -24,9 +25,10 @@
 from acts import test_decorators
 from acts import test_runner
 from acts.controllers.sl4a_lib import rpc_client
+from acts.libs.testtracker.testtracker_results_writer import KEY_EFFORT_NAME
 
 TEST_TRACKER_UUID = '12345'
-UUID_KEY = 'test_tracker_uuid'
+UUID_KEY = 'uuid'
 
 
 def return_true():
@@ -54,9 +56,9 @@
 
 
 class TestDecoratorUnitTests(unittest.TestCase):
-    def _verify_test_tracker_info(self, func):
+    def _verify_test_info(self, func):
         try:
-            test_decorators.test_tracker_info(uuid=TEST_TRACKER_UUID)(func)()
+            test_decorators.test_info(uuid=TEST_TRACKER_UUID)(func)()
             self.fail('Expected decorator to raise exception.')
         except signals.TestSignal as e:
             self.assertTrue(hasattr(e, 'extras'),
@@ -65,23 +67,23 @@
                             'Expected extras to have %s.' % UUID_KEY)
             self.assertEqual(e.extras[UUID_KEY], TEST_TRACKER_UUID)
 
-    def test_test_tracker_info_on_return_true(self):
-        self._verify_test_tracker_info(return_true)
+    def test_test_info_on_return_true(self):
+        self._verify_test_info(return_true)
 
-    def test_test_tracker_info_on_return_false(self):
-        self._verify_test_tracker_info(return_false)
+    def test_test_info_on_return_false(self):
+        self._verify_test_info(return_false)
 
-    def test_test_tracker_info_on_raise_pass(self):
-        self._verify_test_tracker_info(raise_pass)
+    def test_test_info_on_raise_pass(self):
+        self._verify_test_info(raise_pass)
 
-    def test_test_tracker_info_on_raise_failure(self):
-        self._verify_test_tracker_info(raise_failure)
+    def test_test_info_on_raise_failure(self):
+        self._verify_test_info(raise_failure)
 
-    def test_test_tracker_info_on_raise_sl4a(self):
-        self._verify_test_tracker_info(raise_sl4a)
+    def test_test_info_on_raise_sl4a(self):
+        self._verify_test_info(raise_sl4a)
 
-    def test_test_tracker_info_on_raise_generic(self):
-        self._verify_test_tracker_info(raise_generic)
+    def test_test_info_on_raise_generic(self):
+        self._verify_test_info(raise_generic)
 
     def test_test_tracker_info_on_raise_generic_is_chained(self):
         @test_decorators.test_tracker_info(uuid='SOME_UID')
@@ -94,7 +96,7 @@
         self.assertIsInstance(context.exception.__cause__, ValueError)
 
     def test_tti_returns_existing_test_pass(self):
-        @test_decorators.test_tracker_info(uuid='SOME_UID')
+        @test_decorators.test_info(uuid='SOME_UID')
         def test_raises_test_pass():
             raise signals.TestPass('Expected Message')
 
@@ -104,7 +106,7 @@
         self.assertEqual(context.exception.details, 'Expected Message')
 
     def test_tti_returns_existing_test_failure(self):
-        @test_decorators.test_tracker_info(uuid='SOME_UID')
+        @test_decorators.test_info(uuid='SOME_UID')
         def test_raises_test_failure():
             raise signals.TestFailure('Expected Message')
 
@@ -116,7 +118,7 @@
     def test_tti_returns_test_error_on_non_signal_error(self):
         expected_error = ValueError('Some Message')
 
-        @test_decorators.test_tracker_info(uuid='SOME_UID')
+        @test_decorators.test_info(uuid='SOME_UID')
         def test_raises_non_signal():
             raise expected_error
 
@@ -126,7 +128,7 @@
         self.assertEqual(context.exception.details, expected_error)
 
     def test_tti_returns_test_pass_if_no_return_value_specified(self):
-        @test_decorators.test_tracker_info(uuid='SOME_UID')
+        @test_decorators.test_info(uuid='SOME_UID')
         def test_returns_nothing():
             pass
 
@@ -135,7 +137,8 @@
 
     def test_tti_returns_test_fail_if_return_value_is_truthy(self):
         """This is heavily frowned upon. Use signals.TestPass instead!"""
-        @test_decorators.test_tracker_info(uuid='SOME_UID')
+
+        @test_decorators.test_info(uuid='SOME_UID')
         def test_returns_truthy():
             return True
 
@@ -144,7 +147,8 @@
 
     def test_tti_returns_test_fail_if_return_value_is_falsy(self):
         """This is heavily frowned upon. Use signals.TestFailure instead!"""
-        @test_decorators.test_tracker_info(uuid='SOME_UID')
+
+        @test_decorators.test_info(uuid='SOME_UID')
         def test_returns_falsy_but_not_none():
             return False
 
@@ -181,11 +185,111 @@
     TEST_CASE_LIST = 'test_run_mock_test'
     TEST_LOGIC_ATTR = 'test_logic'
 
-    @test_decorators.test_tracker_info(uuid=TEST_TRACKER_UUID)
+    @test_decorators.test_info(uuid=TEST_TRACKER_UUID)
     def test_run_mock_test(self):
         getattr(MockTest, MockTest.TEST_LOGIC_ATTR, None)()
 
 
+class FakeTest(base_test.BaseTestClass):
+    """A fake test class used to test TestTrackerInfoDecoratorFunc."""
+
+    def __init__(self):
+        self.user_params = {
+            'testtracker_properties': 'tt_prop_a=param_a,'
+                                      'tt_prop_b=param_b',
+            'param_a': 'prop_a_value',
+            'param_b': 'prop_b_value',
+        }
+        self.log_path = '/dummy/path'
+        self.begin_time = 1000000
+
+    @test_decorators.test_tracker_info(uuid='SOME_UID')
+    def test_case(self):
+        pass
+
+
+class TestTrackerInfoDecoratorFuncTests(unittest.TestCase):
+
+    @mock.patch('acts.test_decorators.TestTrackerResultsWriter')
+    def test_write_to_testtracker_no_effort_name_no_ad(self, mock_writer):
+        """Should not set the TT Effort name."""
+        with self.assertRaises(signals.TestPass):
+            FakeTest().test_case()
+
+        testtracker_properties = mock_writer.call_args[0][1]
+        self.assertEqual(testtracker_properties,
+                         {'tt_prop_a': 'prop_a_value',
+                          'tt_prop_b': 'prop_b_value'})
+
+    @mock.patch('acts.test_decorators.TestTrackerResultsWriter')
+    def test_write_to_testtracker_no_effort_name_has_ad(self, mock_writer):
+        """Should set the TT Effort Name using the AndroidDevice."""
+        fake_test = FakeTest()
+        fake_test.android_devices = [mock.Mock()]
+        fake_test.android_devices[0].build_info = {'build_id': 'EFFORT_NAME'}
+        with self.assertRaises(signals.TestPass):
+            fake_test.test_case()
+
+        testtracker_properties = mock_writer.call_args[0][1]
+        self.assertEqual(testtracker_properties,
+                         {'tt_prop_a': 'prop_a_value',
+                          'tt_prop_b': 'prop_b_value',
+                          KEY_EFFORT_NAME: 'EFFORT_NAME'})
+
+    @mock.patch('acts.test_decorators.TestTrackerResultsWriter')
+    def test_write_to_testtracker_has_effort_name_has_ad(self, mock_writer):
+        """Should set the TT Effort Name using the AndroidDevice."""
+        fake_test = FakeTest()
+        fake_test.android_devices = [mock.Mock()]
+        fake_test.android_devices[0].build_info = {'build_id': 'BAD_EFFORT'}
+        fake_test.user_params['testtracker_properties'] += (
+                    ',' + KEY_EFFORT_NAME + '=param_effort_name')
+        fake_test.user_params['param_effort_name'] = 'GOOD_EFFORT_NAME'
+
+        with self.assertRaises(signals.TestPass):
+            fake_test.test_case()
+
+        testtracker_properties = mock_writer.call_args[0][1]
+        self.assertEqual(testtracker_properties,
+                         {'tt_prop_a': 'prop_a_value',
+                          'tt_prop_b': 'prop_b_value',
+                          KEY_EFFORT_NAME: 'GOOD_EFFORT_NAME'})
+
+    @mock.patch('acts.test_decorators.TestTrackerResultsWriter')
+    def test_no_testtracker_properties_provided(self, mock_writer):
+        fake_test = FakeTest()
+        del fake_test.user_params['testtracker_properties']
+
+        with self.assertRaises(signals.TestPass):
+            fake_test.test_case()
+
+        self.assertFalse(mock_writer.called)
+
+    @mock.patch('acts.test_decorators.TestTrackerResultsWriter')
+    def test_needs_base_class_set_to_write_to_testtracker(self, mock_writer):
+        class DoesntHaveBaseClass(object):
+            @test_decorators.test_tracker_info(uuid='SOME_UID')
+            def test_case(self):
+                pass
+
+        with self.assertRaises(signals.TestPass):
+            DoesntHaveBaseClass().test_case()
+
+        self.assertFalse(mock_writer.called)
+
+    @mock.patch('acts.test_decorators.TestTrackerResultsWriter')
+    def test_does_not_write_when_decorated_args_are_missing(self, mock_writer):
+        @test_decorators.test_tracker_info(uuid='SOME_UID')
+        def some_decorated_func():
+            pass
+
+        with self.assertRaises(signals.TestPass):
+            some_decorated_func()
+
+        self.assertFalse(mock_writer.called)
+
+
+
 class TestDecoratorIntegrationTests(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
diff --git a/acts/framework/tests/acts_utils_test.py b/acts/framework/tests/acts_utils_test.py
index 9fe757a..7cea04a 100755
--- a/acts/framework/tests/acts_utils_test.py
+++ b/acts/framework/tests/acts_utils_test.py
@@ -177,7 +177,6 @@
 
 class ByPassSetupWizardTests(unittest.TestCase):
     """This test class for unit testing acts.utils.bypass_setup_wizard."""
-
     def test_start_standing_subproc(self):
         with self.assertRaisesRegex(utils.ActsUtilsError,
                                     'Process .* has terminated'):
@@ -311,7 +310,6 @@
 
 class ConcurrentActionsTest(unittest.TestCase):
     """Tests acts.utils.run_concurrent_actions and related functions."""
-
     @staticmethod
     def function_returns_passed_in_arg(arg):
         return arg
@@ -395,7 +393,6 @@
 
 class SuppressLogOutputTest(unittest.TestCase):
     """Tests SuppressLogOutput"""
-
     def test_suppress_log_output(self):
         """Tests that the SuppressLogOutput context manager removes handlers
         of the specified levels upon entry and re-adds handlers upon exit.
@@ -418,7 +415,6 @@
 
 
 class IpAddressUtilTest(unittest.TestCase):
-
     def test_positive_ipv4_normal_address(self):
         ip_address = "192.168.1.123"
         self.assertTrue(utils.is_valid_ipv4_address(ip_address))
diff --git a/acts_tests/tests/google/__init__.py b/acts/framework/tests/controllers/bits_lib/__init__.py
similarity index 100%
copy from acts_tests/tests/google/__init__.py
copy to acts/framework/tests/controllers/bits_lib/__init__.py
diff --git a/acts/framework/tests/controllers/bits_lib/bits_client_test.py b/acts/framework/tests/controllers/bits_lib/bits_client_test.py
new file mode 100644
index 0000000..c4261d6
--- /dev/null
+++ b/acts/framework/tests/controllers/bits_lib/bits_client_test.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.
+
+from datetime import datetime
+import unittest
+
+from acts.controllers.bits_lib import bits_client
+from acts.controllers.bits_lib import bits_service_config
+import mock
+
+CONTROLLER_CONFIG_WITH_MONSOON = {
+    'Monsoon': {'serial_num': 1234, 'monsoon_voltage': 4.2}
+}
+
+MONSOONED_CONFIG = bits_service_config.BitsServiceConfig(
+    CONTROLLER_CONFIG_WITH_MONSOON, lvpm_monsoon_bin='lvpm.par')
+
+CONTROLLER_CONFIG_WITHOUT_MONSOON = {}
+
+NON_MONSOONED_CONFIG = bits_service_config.BitsServiceConfig(
+    CONTROLLER_CONFIG_WITHOUT_MONSOON)
+
+
+class BitsClientTest(unittest.TestCase):
+
+    def setUp(self):
+        super().setUp()
+        self.mock_service = mock.Mock()
+        self.mock_service.port = '42'
+        self.mock_active_collection = mock.Mock()
+        self.mock_active_collection.name = 'my_active_collection'
+        self.mock_active_collection.markers_buffer = []
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_start_collection__without_monsoon__does_not_disconnect_monsoon(
+        self,
+        mock_run):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=NON_MONSOONED_CONFIG)
+
+        client.start_collection()
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        non_expected_call = list(
+            filter(lambda call: 'usb_disconnect' in call.args[0],
+                   args_list))
+        self.assertEqual(len(non_expected_call), 0,
+                         'did not expect call with usb_disconnect')
+
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.job.run')
+    def test_stop_collection__usb_not_automanaged__does_not_connect_monsoon(
+        self,
+        mock_run,
+        mock_context):
+        output_path = mock.MagicMock(return_value='out')
+        mock_context.side_effect = lambda: output_path
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        client._active_collection = self.mock_active_collection
+
+        client.stop_collection()
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        non_expected_call = list(
+            filter(lambda call: 'usb_connect' in call.args[0], args_list))
+        self.assertEquals(len(non_expected_call), 0,
+                          'did not expect call with usb_connect')
+
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.job.run')
+    def test_stop_collection__triggers_export(self, mock_run, mock_context):
+        output_path = mock.MagicMock(return_value='out')
+        mock_context.side_effect = lambda: output_path
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        client._active_collection = self.mock_active_collection
+
+        client.stop_collection()
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_call = list(
+            filter(lambda call: '--export' in call.args[0], args_list))
+        self.assertEqual(len(expected_call), 1,
+                         'expected a call with --export')
+
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.job.run')
+    def test__export_ignores_dataseries_gaps(self, mock_run, mock_context):
+        output_path = mock.MagicMock(return_value='out')
+        mock_context.side_effect = lambda: output_path
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        client._active_collection = self.mock_active_collection
+
+        client._export()
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_call = list(
+            filter(
+                lambda call: '--ignore_gaps' in call.args[0] and '--export' in
+                             call.args[0], args_list))
+        self.assertEqual(len(expected_call), 1,
+                         'expected a call with --ignore_gaps and --export')
+        self.assertIn('--ignore_gaps', expected_call[0].args[0])
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_add_marker(self, _):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        client._active_collection = self.mock_active_collection
+
+        client.add_marker(7133, 'my marker')
+
+        client._active_collection.add_marker.assert_called_with(7133,
+                                                                'my marker')
+
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.job.run')
+    def test_stop_collection__flushes_buffered_markers(self, mock_run,
+                                                       mock_context):
+        output_path = mock.MagicMock(return_value='out')
+        mock_context.side_effect = lambda: output_path
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        self.mock_active_collection.markers_buffer.append((3, 'tres'))
+        self.mock_active_collection.markers_buffer.append((1, 'uno'))
+        self.mock_active_collection.markers_buffer.append((2, 'dos'))
+        client._active_collection = self.mock_active_collection
+
+        client.stop_collection()
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_calls = list(
+            filter(lambda call: '--log' in call.args[0], args_list))
+        self.assertEqual(len(expected_calls), 3,
+                         'expected 3 calls with --log')
+        self.assertIn('--log_ts', expected_calls[0][0][0])
+        self.assertIn('1', expected_calls[0][0][0])
+        self.assertIn('uno', expected_calls[0][0][0])
+        self.assertIn('--log_ts', expected_calls[1][0][0])
+        self.assertIn('2', expected_calls[1][0][0])
+        self.assertIn('dos', expected_calls[1][0][0])
+        self.assertIn('--log_ts', expected_calls[2][0][0])
+        self.assertIn('3', expected_calls[2][0][0])
+        self.assertIn('tres', expected_calls[2][0][0])
+        self.mock_active_collection.clear_markers_buffer.assert_called()
+
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.job.run')
+    def test_stop_collection__flushes_buffered_datetime_markers(self,
+                                                                mock_run,
+                                                                mock_context):
+        output_path = mock.MagicMock(return_value='out')
+        mock_context.side_effect = lambda: output_path
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        self.mock_active_collection.markers_buffer.append(
+            (datetime.utcfromtimestamp(3), 'tres'))
+        self.mock_active_collection.markers_buffer.append(
+            (datetime.utcfromtimestamp(1), 'uno'))
+        self.mock_active_collection.markers_buffer.append(
+            (datetime.utcfromtimestamp(2), 'dos'))
+        client._active_collection = self.mock_active_collection
+
+        client.stop_collection()
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_calls = list(
+            filter(lambda call: '--log' in call.args[0], args_list))
+        self.assertEqual(len(expected_calls), 3,
+                         'expected 3 calls with --log')
+        self.assertIn('--log_ts', expected_calls[0][0][0])
+        self.assertIn(str(int(1e9)), expected_calls[0][0][0])
+        self.assertIn('uno', expected_calls[0][0][0])
+        self.assertIn('--log_ts', expected_calls[1][0][0])
+        self.assertIn(str(int(2e9)), expected_calls[1][0][0])
+        self.assertIn('dos', expected_calls[1][0][0])
+        self.assertIn('--log_ts', expected_calls[2][0][0])
+        self.assertIn(str(int(3e9)), expected_calls[2][0][0])
+        self.assertIn('tres', expected_calls[2][0][0])
+        self.mock_active_collection.clear_markers_buffer.assert_called()
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_get_metrics(self, mock_run):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        client._active_collection = self.mock_active_collection
+
+        client.get_metrics(8888, 9999)
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_call = list(
+            filter(lambda call: '--aggregates_yaml_path' in call.args[0],
+                   args_list))
+        self.assertEqual(len(expected_call), 1,
+                         'expected a call with --aggregates_yaml_path')
+        self.assertIn('8888', expected_call[0][0][0])
+        self.assertIn('--ignore_gaps', expected_call[0][0][0])
+        self.assertIn('--abs_stop_time', expected_call[0][0][0])
+        self.assertIn('9999', expected_call[0][0][0])
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_get_metrics_with_datetime_markers(self, mock_run):
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=MONSOONED_CONFIG)
+        client._active_collection = self.mock_active_collection
+
+        client.get_metrics(datetime.utcfromtimestamp(1),
+                           datetime.utcfromtimestamp(2))
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_call = list(
+            filter(lambda call: '--aggregates_yaml_path' in call.args[0],
+                   args_list))
+        self.assertEqual(len(expected_call), 1,
+                         'expected a call with --aggregates_yaml_path')
+        self.assertIn(str(int(1e9)), expected_call[0][0][0])
+        self.assertIn('--ignore_gaps', expected_call[0][0][0])
+        self.assertIn('--abs_stop_time', expected_call[0][0][0])
+        self.assertIn(str(int(2e9)), expected_call[0][0][0])
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_get_metrics_with_virtual_metrics_file(self, mock_run):
+        service_config = mock.Mock()
+        service_config.has_virtual_metrics_file = True
+        client = bits_client.BitsClient('bits.par', self.mock_service,
+                                        service_config=service_config)
+        client._active_collection = self.mock_active_collection
+
+        client.get_metrics(8888, 9999)
+
+        mock_run.assert_called()
+        args_list = mock_run.call_args_list
+        expected_call = list(
+            filter(lambda call: '--aggregates_yaml_path' in call.args[0],
+                   args_list))
+        self.assertEqual(len(expected_call), 1,
+                         'expected a call with --aggregates_yaml_path')
+        self.assertIn('--vm_file', expected_call[0][0][0])
+        self.assertIn('default', expected_call[0][0][0])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/controllers/bits_lib/bits_service_config_test.py b/acts/framework/tests/controllers/bits_lib/bits_service_config_test.py
new file mode 100644
index 0000000..fda4f64
--- /dev/null
+++ b/acts/framework/tests/controllers/bits_lib/bits_service_config_test.py
@@ -0,0 +1,192 @@
+#!/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 unittest
+
+from acts.controllers.bits_lib import bits_service_config
+
+
+class BitsServiceConfigTest(unittest.TestCase):
+
+    def test_basic_config(self):
+        config_dic = bits_service_config.BitsServiceConfig({}).config_dic
+        self.assertIn('devices', config_dic)
+        self.assertIn('default_device', config_dic['devices'])
+        self.assertIn('collectors', config_dic['devices']['default_device'])
+
+    def test_bits_service_config_has_an_enabled_default_device(self):
+        config_dic = bits_service_config.BitsServiceConfig({}).config_dic
+        self.assertEqual(1, config_dic['devices']['default_device']['enabled'])
+
+
+class BitsServiceConfigWithMonsoonTest(unittest.TestCase):
+
+    def test_monsoon_with_serial_less_than_20000_is_configured_as_non_hv(self):
+        config = bits_service_config._BitsMonsoonConfig(
+            {'serial_num': 19999, 'monsoon_voltage': 1},
+            lvpm_monsoon_bin='lvpm_bin', hvpm_monsoon_bin='hvpm_bin')
+        self.assertEqual(0, config.config_dic['hv_monsoon'])
+        self.assertEqual('lvpm_bin', config.config_dic['monsoon_binary_path'])
+
+    def test_lvpm_monsoon_requires_lvpm_binary(self):
+        self.assertRaisesRegex(ValueError,
+                               r'lvpm_monsoon binary is needed but was None.',
+                               bits_service_config._BitsMonsoonConfig,
+                               {'serial_num': 19999, 'monsoon_voltage': 1},
+                               hvpm_monsoon_bin='hvpm_bin')
+
+    def test_monsoon_with_serial_greater_than_20000_is_configured_as_hv(self):
+        config = bits_service_config._BitsMonsoonConfig(
+            {'serial_num': 20001, 'monsoon_voltage': 1},
+            lvpm_monsoon_bin='lvpm_bin', hvpm_monsoon_bin='hvpm_bin')
+        self.assertEqual(1, config.config_dic['hv_monsoon'])
+        self.assertEqual('hvpm_bin', config.config_dic['monsoon_binary_path'])
+
+    def test_hvpm_monsoon_requires_hvpm_binary(self):
+        self.assertRaisesRegex(ValueError,
+                               r'hvpm_monsoon binary is needed but was None.',
+                               bits_service_config._BitsMonsoonConfig,
+                               {'serial_num': 20001, 'monsoon_voltage': 1},
+                               lvpm_monsoon_bin='hvpm_bin')
+
+    def test_monsoon_config_fails_without_voltage(self):
+        self.assertRaisesRegex(ValueError,
+                               r'Monsoon voltage can not be undefined.',
+                               bits_service_config._BitsMonsoonConfig,
+                               {'serial_num': 1},
+                               lvpm_monsoon_bin='lvpm_bin')
+
+    def test_monsoon_config_fails_without_serial(self):
+        self.assertRaisesRegex(ValueError,
+                               r'Monsoon serial_num can not be undefined.',
+                               bits_service_config._BitsMonsoonConfig,
+                               {'monsoon_voltage': 1},
+                               lvpm_monsoon_bin='lvpm_bin')
+
+    def test_monsoon_config_is_always_enabled(self):
+        config = bits_service_config._BitsMonsoonConfig(
+            {'serial_num': 1, 'monsoon_voltage': 1},
+            lvpm_monsoon_bin='bin')
+        self.assertEqual(1, config.config_dic['enabled'])
+
+    def test_monsoon_config_disables_monsoon_reseting(self):
+        config = bits_service_config._BitsMonsoonConfig(
+            {'serial_num': 1, 'monsoon_voltage': 1},
+            lvpm_monsoon_bin='bin')
+        self.assertEqual(0, config.config_dic['monsoon_reset'])
+
+    def test_monsoon_config_type_is_monsooncollector(self):
+        config = bits_service_config._BitsMonsoonConfig(
+            {'serial_num': 1, 'monsoon_voltage': 1},
+            lvpm_monsoon_bin='bin')
+        self.assertEqual('monsooncollector', config.config_dic['type'])
+
+    def test_bits_service_config_without_monsoon(self):
+        service_config = bits_service_config.BitsServiceConfig({})
+        self.assertFalse(service_config.has_monsoon)
+
+    def test_bits_service_config_with_a_monsoon(self):
+        service_config = bits_service_config.BitsServiceConfig(
+            {'Monsoon': {'serial_num': 1, 'monsoon_voltage': 1}},
+            lvpm_monsoon_bin='bin')
+        config_dic = service_config.config_dic
+
+        self.assertTrue(service_config.has_monsoon)
+        self.assertIn('Monsoon',
+                      config_dic['devices']['default_device'][
+                          'collectors'])
+
+        monsoon_config = bits_service_config._BitsMonsoonConfig(
+            {'serial_num': 1, 'monsoon_voltage': 1},
+            lvpm_monsoon_bin='bin').config_dic
+        self.assertEqual(monsoon_config,
+                         config_dic['devices']['default_device'][
+                             'collectors']['Monsoon'])
+
+
+class BitsServiceConfigWithKibblesTest(unittest.TestCase):
+    def test_bits_service_config_without_kibbles(self):
+        service_config = bits_service_config.BitsServiceConfig({})
+        self.assertFalse(service_config.has_kibbles)
+
+    def test_bits_service_config_with_kibbles_but_no_vm_files(self):
+        service_config = bits_service_config.BitsServiceConfig({'Kibbles': [
+            {'board': 'BOARD', 'connector': 'CONNECTOR', 'serial': 'SERIAL'}]},
+            kibble_bin='bin',
+            kibble_board_file='file.board')
+
+        self.assertFalse(service_config.has_virtual_metrics_file)
+
+    def test_bits_service_config_with_kibbles_and_vm_files(self):
+        service_config = bits_service_config.BitsServiceConfig({'Kibbles': [
+            {'board': 'BOARD', 'connector': 'CONNECTOR', 'serial': 'SERIAL'}]},
+            kibble_bin='bin',
+            kibble_board_file='file.board',
+            virtual_metrics_file='some_file.vm')
+        config_dic = service_config.config_dic
+
+        self.assertTrue(service_config.has_virtual_metrics_file)
+        self.assertIn('some_file.vm',
+                      config_dic['devices']['default_device']['vm_files'])
+
+    def test_bits_service_config_with_kibbles(self):
+        service_config = bits_service_config.BitsServiceConfig({'Kibbles': [
+            {'board': 'BOARD', 'connector': 'CONNECTOR', 'serial': 'SERIAL'}]},
+            kibble_bin='bin',
+            kibble_board_file='file.board')
+        config_dic = service_config.config_dic
+
+        self.assertTrue(service_config.has_kibbles)
+        self.assertIn('BOARD',
+                      config_dic['devices']['default_device']['collectors'])
+
+        boards_config = bits_service_config._BitsKibblesConfig([
+            {'board': 'BOARD', 'connector': 'CONNECTOR', 'serial': 'SERIAL'}],
+            kibble_bin='bin', kibble_board_file='file.board').boards_configs
+        self.assertEqual(boards_config['BOARD'],
+                         config_dic['devices']['default_device'][
+                             'collectors']['BOARD'])
+
+    def test_kibbles_get_grouped_by_board(self):
+        boards_config = bits_service_config._BitsKibblesConfig([
+            {'board': 'BOARD1', 'connector': 'A', 'serial': 'SERIAL1'},
+            {'board': 'BOARD2', 'connector': 'B', 'serial': 'SERIAL2'},
+            {'board': 'BOARD2', 'connector': 'C', 'serial': 'SERIAL3'}],
+            kibble_bin='bin',
+            kibble_board_file='file.board').boards_configs
+
+        self.assertIn('BOARD1', boards_config)
+        board1 = boards_config['BOARD1']
+        self.assertEqual(1, len(board1['attached_kibbles']))
+        self.assertIn('SERIAL1', board1['attached_kibbles'])
+
+        self.assertIn('BOARD2', boards_config)
+        board2 = boards_config['BOARD2']
+        self.assertEqual(2, len(board2['attached_kibbles']))
+        self.assertIn('SERIAL2', board2['attached_kibbles'])
+        self.assertIn('SERIAL3', board2['attached_kibbles'])
+
+    def test_kibble_config_type_is_kibblecollector(self):
+        board_config = bits_service_config._BitsKibblesConfig([
+            {'board': 'BOARD', 'connector': 'CONNECTOR', 'serial': 'SERIAL'}],
+            kibble_bin='bin',
+            kibble_board_file='file.board').boards_configs['BOARD']
+
+        self.assertEqual('kibblecollector', board_config['type'])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/controllers/bits_lib/bits_service_test.py b/acts/framework/tests/controllers/bits_lib/bits_service_test.py
new file mode 100644
index 0000000..63d7c47
--- /dev/null
+++ b/acts/framework/tests/controllers/bits_lib/bits_service_test.py
@@ -0,0 +1,144 @@
+#!/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 unittest
+
+from acts.controllers.bits_lib import bits_service
+from acts.controllers.bits_lib import bits_service_config
+import mock
+
+SERVICE_CONFIG = bits_service_config.BitsServiceConfig(
+    {'Monsoon': {'serial_num': 538141, 'monsoon_voltage': 4.2}},
+    hvpm_monsoon_bin='hvpm.par')
+
+
+@mock.patch('acts.controllers.bits_lib.bits_service.atexit')
+@mock.patch('builtins.open')
+class BitsServiceTest(unittest.TestCase):
+    def test_output_log_opens_on_creation(self, mock_open, *_):
+        bits_service.BitsService(SERVICE_CONFIG, 'binary', 'log_path')
+
+        mock_open.assert_called_with('log_path', 'w')
+
+    @mock.patch.object(bits_service.BitsService, '_write_extra_debug_logs')
+    @mock.patch('acts.libs.proc.job.run')
+    def test_output_log_gets_closed_on_cleanup(self, _, __, mock_open, *___):
+        mock_log = mock.Mock()
+        mock_open.return_value = mock_log
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+        service._cleanup()
+
+        mock_log.close.assert_called_with()
+
+    @mock.patch('acts.libs.proc.job.run')
+    def test_monsoons_usb_gets_connected_on_cleanup(self, mock_run, *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+
+        service._cleanup()
+
+        mock_run.assert_called()
+        self.assertIn('--usbpassthrough', mock_run.call_args[0][0])
+        self.assertIn('on', mock_run.call_args[0][0])
+
+    def test_service_can_not_be_started_twice(self, *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+        service.service_state = bits_service.BitsServiceStates.STARTED
+        with self.assertRaises(bits_service.BitsServiceError):
+            service.start()
+
+    def test_service_can_not_be_stoped_twice(self, *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+        service.service_state = bits_service.BitsServiceStates.STOPPED
+        with self.assertRaises(bits_service.BitsServiceError):
+            service.stop()
+
+    def test_stopped_service_can_not_be_started(self, *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+        service.service_state = bits_service.BitsServiceStates.STOPPED
+        with self.assertRaises(bits_service.BitsServiceError):
+            service.start()
+
+    def test_service_output_changes_service_reported_state(self, *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+        self.assertEqual(bits_service.BitsServiceStates.NOT_STARTED,
+                         service.service_state)
+
+        service.port = '1234'
+        service._output_callback('Started server!')
+
+        self.assertEqual(bits_service.BitsServiceStates.STARTED,
+                         service.service_state)
+
+    def test_service_output_defines_port(self, *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+
+        service._output_callback('Server listening on ...:6174.')
+
+        self.assertIsNotNone(service.port)
+
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.process.Process')
+    def test_top_level_call_is_timeout_if_timeout_is_defined(self, mock_process,
+                                                             *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path',
+                                           timeout=42)
+
+        def side_effect(*_, **__):
+            service.service_state = bits_service.BitsServiceStates.STARTED
+            return mock.Mock()
+
+        mock_process.side_effect = side_effect
+
+        service.start()
+
+        args, kwargs = mock_process.call_args
+        self.assertEqual('timeout', args[0][0])
+        self.assertEqual('--signal=SIGTERM', args[0][1])
+        self.assertEqual('--kill-after=60', args[0][2])
+        self.assertEqual('42', args[0][3])
+        self.assertEqual('binary', args[0][4])
+
+    @mock.patch.object(bits_service.BitsService, '_write_extra_debug_logs')
+    @mock.patch('acts.context.get_current_context')
+    @mock.patch('acts.libs.proc.process.Process')
+    def test_top_level_call_is_binary_if_timeout_is_not_defined(self,
+                                                                mock_process,
+                                                                *_):
+        service = bits_service.BitsService(SERVICE_CONFIG, 'binary',
+                                           'log_path')
+
+        def side_effect(*_, **__):
+            service.service_state = bits_service.BitsServiceStates.STARTED
+            return mock.Mock()
+
+        mock_process.side_effect = side_effect
+
+        service.start()
+
+        args, kwargs = mock_process.call_args
+        self.assertEqual('binary', args[0][0])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/controllers/bits_test.py b/acts/framework/tests/controllers/bits_test.py
new file mode 100644
index 0000000..e437bea
--- /dev/null
+++ b/acts/framework/tests/controllers/bits_test.py
@@ -0,0 +1,96 @@
+#!/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 unittest
+from acts.controllers import bits
+from acts.controllers import power_metrics
+
+
+class BitsTest(unittest.TestCase):
+
+    def test_metric_name_transformation_for_legacy_support(self):
+        avg_current = bits._transform_name('default_name.Monsoon.Monsoon:mA')
+        avg_power = bits._transform_name('default_name.Monsoon.Monsoon:mW')
+
+        self.assertEqual('avg_current', avg_current)
+        self.assertEqual('avg_power', avg_power)
+
+    def test_metric_name_transformation(self):
+        avg_current = bits._transform_name('default_name.slider.XYZ:mA')
+        avg_power = bits._transform_name('default_name.slider.ABCD:mW')
+        unknown_unit = bits._transform_name('default_name.aaaaa.QWERTY:unknown')
+
+        self.assertEqual('XYZ_avg_current', avg_current)
+        self.assertEqual('ABCD_avg_power', avg_power)
+        self.assertEqual('QWERTY', unknown_unit)
+
+    def test_raw_data_to_metrics(self):
+        raw_data = {'data': [
+            {'name': 'default_device.Monsoon.Monsoon:mA',
+             'avg': 21,
+             'unit': 'mA'},
+            {'name': 'default_device.Monsoon.Monsoon:mW',
+             'avg': 91,
+             'unit': 'mW'}]}
+
+        metrics = bits._raw_data_to_metrics(raw_data)
+        self.assertEqual(2, len(metrics))
+        self.assertEqual(
+            power_metrics.Metric(21, 'current', 'mA', 'avg_current'),
+            metrics[0])
+        self.assertEqual(
+            power_metrics.Metric(91, 'power', 'mW', 'avg_power'),
+            metrics[1])
+
+    def test_raw_data_to_metrics_messages_are_ignored(self):
+        raw_data = {'data': [
+            {'name': 'default_device.Log.UserInputs',
+             'avg': float('nan'),
+             'unit': 'Msg'},
+            {'name': 'default_device.Log.Warnings',
+             'avg': float('nan'),
+             'unit': 'Msg'}]}
+
+        metrics = bits._raw_data_to_metrics(raw_data)
+        self.assertEqual(0, len(metrics))
+
+    def test_get_single_file_get_first_element_of_a_list(self):
+        registry = {'some_key': ['first_element', 'second_element']}
+
+        result = bits._get_single_file(registry, 'some_key')
+
+        self.assertEqual('first_element', result)
+
+    def test_get_single_file_gets_string_if_registry_contains_string(self):
+        registry = {'some_key': 'this_is_a_string'}
+
+        result = bits._get_single_file(registry, 'some_key')
+
+        self.assertEqual('this_is_a_string', result)
+
+    def test_get_single_file_gets_none_if_value_is_undefined_or_empty_list(self):
+        registry = {'some_key': []}
+
+        result1 = bits._get_single_file(registry, 'some_key')
+        result2 = bits._get_single_file(registry, 'key_that_is_not_in_registry')
+
+        self.assertEqual(None, result1)
+        self.assertEqual(None, result2)
+
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/tests/google/__init__.py b/acts/framework/tests/libs/testtracker/__init__.py
similarity index 100%
copy from acts_tests/tests/google/__init__.py
copy to acts/framework/tests/libs/testtracker/__init__.py
diff --git a/acts/framework/tests/libs/testtracker/testtracker_results_writer_test.py b/acts/framework/tests/libs/testtracker/testtracker_results_writer_test.py
new file mode 100644
index 0000000..a976f6f
--- /dev/null
+++ b/acts/framework/tests/libs/testtracker/testtracker_results_writer_test.py
@@ -0,0 +1,188 @@
+#!/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 unittest
+
+import mock
+from acts.libs.testtracker.protos.gen.testtracker_result_pb2 import Result
+from acts.libs.testtracker.testtracker_results_writer import KEY_DETAILS
+from acts.libs.testtracker.testtracker_results_writer import KEY_EFFORT_NAME
+from acts.libs.testtracker.testtracker_results_writer import KEY_PROJECT_ID
+from acts.libs.testtracker.testtracker_results_writer import \
+    KEY_TESTTRACKER_UUID
+from acts.libs.testtracker.testtracker_results_writer import KEY_USER
+from acts.libs.testtracker.testtracker_results_writer import KEY_UUID
+from acts.libs.testtracker.testtracker_results_writer import \
+    TestTrackerError
+from acts.libs.testtracker.testtracker_results_writer import \
+    TestTrackerResultsWriter
+from acts.records import TestResultRecord
+from acts.signals import TestSignal
+
+MOCK_PROPERTIES = {
+    KEY_USER: 'sample_user',
+    KEY_PROJECT_ID: 12345,
+    KEY_EFFORT_NAME: 'sample_effort'
+}
+MOCK_SIGNAL = TestSignal(
+    'Sample error message', {KEY_TESTTRACKER_UUID: '12345abcde'})
+MOCK_LOG_PATH = 'sample_logs'
+
+
+class TestTrackerResultsWriterTest(unittest.TestCase):
+    """Unit tests for TestTrackerResultsWriter"""
+
+    def setUp(self):
+        self.mock_proto = mock.MagicMock()
+        self.mock_prop = mock.MagicMock()
+        self.mock_proto.property.add.return_value = self.mock_prop
+        self.mock_record = TestResultRecord('sample_test')
+        self.mock_record.begin_time = 0
+        self.mock_record.test_pass(MOCK_SIGNAL)
+
+    def test_validate_properties_raises_on_missing_required_props(self):
+        """Test that _validate_properties raises an exception if not all of the
+        required properties are specified.
+        """
+        invalid_props = {'property1': 'value1'}
+        with self.assertRaisesRegex(TestTrackerError, 'required properties'):
+            _ = TestTrackerResultsWriter(MOCK_LOG_PATH, invalid_props)
+
+    def test_add_property_with_bool_value(self):
+        """Test that adding a bool Property to a Result correctly sets the
+        Property value.
+        """
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        writer._add_property(self.mock_proto, 'bool_prop', False)
+        self.assertEqual(self.mock_prop.bool_value, False)
+
+    def test_add_property_with_int_value(self):
+        """Test that adding a int Property to a Result correctly sets the
+        Property value.
+        """
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        writer._add_property(self.mock_proto, 'int_prop', 5)
+        self.assertEqual(self.mock_prop.int_value, 5)
+
+    def test_add_property_with_float_value(self):
+        """Test that adding a float Property to a Result correctly sets the
+        Property value.
+        """
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        writer._add_property(self.mock_proto, 'float_prop', 7.4)
+        self.assertEqual(self.mock_prop.double_value, 7.4)
+
+    def test_add_property_with_str_value(self):
+        """Test that adding a str Property to a Result correctly sets the
+        Property value.
+        """
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        writer._add_property(self.mock_proto, 'str_prop', 'ok')
+        self.assertEqual(self.mock_prop.string_value, 'ok')
+
+    def test_create_result_proto_sets_correct_uuid(self):
+        """Test that _create_result_proto sets the correct uuid from the test
+        result record.
+        """
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        self.assertEqual(result.uuid, '12345abcde')
+        for prop in result.property:
+            if prop.name == KEY_UUID:
+                self.assertEqual(prop.string_value, '12345abcde')
+                return
+        self.fail('Property "%s" missing from Result proto.' % KEY_UUID)
+
+    def test_create_result_proto_sets_status_to_passed(self):
+        """Test that _create_result_proto correctly sets status for a test with
+        result=PASS.
+        """
+        self.mock_record.test_pass(MOCK_SIGNAL)
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        self.assertEqual(result.status, Result.PASSED)
+
+    def test_create_result_proto_sets_status_to_failed(self):
+        """Test that _create_result_proto correctly sets status for a test with
+        result=FAIL.
+        """
+        self.mock_record.test_fail(MOCK_SIGNAL)
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        self.assertEqual(result.status, Result.FAILED)
+
+    def test_create_result_proto_sets_status_to_skipped(self):
+        """Test that _create_result_proto correctly sets status for a test with
+        result=SKIP.
+        """
+        self.mock_record.test_skip(MOCK_SIGNAL)
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        self.assertEqual(result.status, Result.SKIPPED)
+
+    def test_create_result_proto_sets_status_to_error(self):
+        """Test that _create_result_proto correctly sets status for a test with
+        result=ERROR.
+        """
+        self.mock_record.test_error(MOCK_SIGNAL)
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        self.assertEqual(result.status, Result.ERROR)
+
+    def test_create_result_proto_sets_correct_timestamp(self):
+        """Test that _create_result_proto sets the correct timestamp format
+        given begin_time (in ms).
+        """
+        self.mock_record.begin_time = 1579230033639
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        self.assertEqual(result.timestamp, '2020-01-17T03:00:33.639Z')
+
+    def test_create_result_proto_sets_correct_details(self):
+        """Test that _create_result_proto correctly sets the details Property
+        from the test signal.
+        """
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        result = writer._create_result_proto(self.mock_record)
+        for prop in result.property:
+            if prop.name == KEY_DETAILS:
+                self.assertEqual(prop.string_value, 'Sample error message')
+                return
+        self.fail('Property "%s" missing from Result proto.' % KEY_DETAILS)
+
+    def test_create_result_proto_sets_additional_properties(self):
+        """Test that _create_result_proto correctly sets extra properties."""
+        writer = TestTrackerResultsWriter(
+            MOCK_LOG_PATH, dict(**MOCK_PROPERTIES, extra_prop='foo'))
+        result = writer._create_result_proto(self.mock_record)
+        for prop in result.property:
+            if prop.name == 'extra_prop':
+                self.assertEqual(prop.string_value, 'foo')
+                return
+        self.fail('Property "extra_prop" missing from Result proto.')
+
+    @mock.patch('os.makedirs')
+    def test_create_results_dir(self, _):
+        """Test that _create_results_dir generates the correct path."""
+        writer = TestTrackerResultsWriter(MOCK_LOG_PATH, MOCK_PROPERTIES)
+        self.assertEqual(
+            writer._create_results_dir('23456bcdef'),
+            'sample_logs/test_tracker_results/test_effort_name=sample_effort/'
+            'test_case_uuid=23456bcdef')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py
index 110b94f..ee4c240 100644
--- a/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py
@@ -483,17 +483,23 @@
         if response.get('error'):
             raise ConnectionError(
                 'Failed to get client network connection status')
-
         result = response.get('result')
-        if result and isinstance(result, dict) and result.get('Connected'):
-            if ssid and result['Connected'].get('ssid'):
+        if isinstance(result, dict):
+            connected_to = result.get('Connected')
+            # TODO(https://fxbug.dev/85938): Remove backwards compatibility once
+            # ACTS is versioned with Fuchsia.
+            if not connected_to:
+                connected_to = result.get('connected_to')
+            if not connected_to:
+                return False
+
+            if ssid:
                 # Replace encoding errors instead of raising an exception.
                 # Since `ssid` is a string, this will not affect the test
                 # for equality.
-                connected_ssid = bytearray(result['Connected']['ssid']).decode(
+                connected_ssid = bytearray(connected_to['ssid']).decode(
                     encoding='utf-8', errors='replace')
-                if ssid != connected_ssid:
-                    return False
+                return ssid == connected_ssid
             return True
         return False
 
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device_lib/AbstractDeviceWlanDeviceBaseTest.py b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device_lib/AbstractDeviceWlanDeviceBaseTest.py
index 239accf..e90c527 100644
--- a/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device_lib/AbstractDeviceWlanDeviceBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device_lib/AbstractDeviceWlanDeviceBaseTest.py
@@ -15,11 +15,22 @@
 #   limitations under the License.
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
+from mobly import utils
+from mobly.base_test import STAGE_NAME_TEARDOWN_CLASS
+
 
 class AbstractDeviceWlanDeviceBaseTest(WifiBaseTest):
     def setup_class(self):
         super().setup_class()
 
+    def teardown_class(self):
+        begin_time = utils.get_current_epoch_time()
+        super().teardown_class()
+        for device in getattr(self, "android_devices", []):
+            device.take_bug_report(STAGE_NAME_TEARDOWN_CLASS, begin_time)
+        for device in getattr(self, "fuchsia_devices", []):
+            device.take_bug_report(STAGE_NAME_TEARDOWN_CLASS, begin_time)
+
     def on_fail(self, test_name, begin_time):
         try:
             self.dut.get_log(test_name, begin_time)
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_carkit_lib.py b/acts_tests/acts_contrib/test_utils/bt/bt_carkit_lib.py
index 14a42b0..959e65a 100644
--- a/acts_tests/acts_contrib/test_utils/bt/bt_carkit_lib.py
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_carkit_lib.py
@@ -30,7 +30,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
 from acts_contrib.test_utils.tel.tel_test_utils import num_active_calls
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call
 from acts_contrib.test_utils.tel.tel_voice_utils import get_audio_route
 from acts_contrib.test_utils.tel.tel_voice_utils import set_audio_route
diff --git a/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py b/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py
index 6dfa77d..a685b65 100644
--- a/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py
@@ -23,6 +23,7 @@
 MDLOG_SETTLING_TIME = 2
 MDLOG_PROCESS_KILL_TIME = 3
 NOHUP_CMD = "nohup diag_mdlog -f {} -o {} -s 100 -c &> /dev/null &"
+DEVICE_GPSLOG_FOLDER = '/sdcard/Android/data/com.android.gpstool/files/'
 
 
 def find_device_qxdm_log_mask(ad, maskfile):
@@ -154,3 +155,23 @@
         # errno.ESRCH - No such process
         raise ProcessLookupError(errno.ESRCH, os.strerror(errno.ESRCH),
                                  "diag_mdlog")
+
+
+def get_gpstool_logs(ad, local_logpath, keep_logs=True):
+    """
+
+    Pulls gpstool Logs from android device
+
+       Args:
+           ad: the target android device, AndroidDevice object
+           local_logpath: Local file path to pull the gpstool logs
+           keep_logs: False, delete log files from the gpstool log path
+    """
+
+    gps_log_path = os.path.join(local_logpath, 'GPSLogs')
+    ad.adb.pull("{} {}".format(DEVICE_GPSLOG_FOLDER, gps_log_path))
+    ad.log.debug("gpstool logs are pulled from device")
+
+    if not keep_logs:
+        ad.adb.shell("rm -rf " + DEVICE_GPSLOG_FOLDER + "*.*")
+        ad.log.debug("gpstool logs are deleted from device")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/net/NetstackBaseTest.py b/acts_tests/acts_contrib/test_utils/net/NetstackBaseTest.py
old mode 100644
new mode 100755
diff --git a/acts_tests/acts_contrib/test_utils/net/connectivity_const.py b/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
index 3ea9967..591c83f 100644
--- a/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
+++ b/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
@@ -42,6 +42,7 @@
 NETWORK_CB_KEY_CREATE_TS = "creation_timestamp"
 NETWORK_CB_KEY_CURRENT_TS = "current_timestamp"
 NETWORK_CB_KEY_NETWORK_SPECIFIER = "network_specifier"
+NETWORK_CB_KEY_TRANSPORT_INFO = "transport_info"
 
 # Constants for VPN connection status
 VPN_STATE_DISCONNECTED = 0
@@ -58,6 +59,14 @@
 TYPE_MOBILE = 0
 TYPE_WIFI = 1
 
+# Network request related constants.
+NETWORK_CAP_TRANSPORT_WIFI = TYPE_WIFI
+NETWORK_CAP_CAPABILITY_INTERNET = 12
+
+# Network request related keys.
+NETWORK_CAP_TRANSPORT_TYPE_KEY = "TransportType"
+NETWORK_CAP_CAPABILITY_KEY = "Capability"
+
 # Multipath preference constants
 MULTIPATH_PREFERENCE_NONE = 0
 MULTIPATH_PREFERENCE_HANDOVER = 1 << 0
diff --git a/acts_tests/acts_contrib/test_utils/net/connectivity_test_utils.py b/acts_tests/acts_contrib/test_utils/net/connectivity_test_utils.py
index d35fe04..1b547e3 100644
--- a/acts_tests/acts_contrib/test_utils/net/connectivity_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/net/connectivity_test_utils.py
@@ -104,7 +104,14 @@
     msg = "Failed to receive confirmation of stopping socket keepalive"
     return _listen_for_keepalive_event(ad, key, msg, "Stopped")
 
+
 def set_private_dns(ad, dns_mode, hostname=None):
+    """ Set private DNS mode and DNS server hostname on DUT
+
+    :param ad: Device under test (DUT)
+    :param dns_mode: DNS mode, including OFF, OPPORTUNISTIC, STRICT
+    :param hostname: DNS server hostname
+    """
     """ Set private DNS mode on dut """
     if dns_mode == cconst.PRIVATE_DNS_MODE_OFF:
         ad.droid.setPrivateDnsMode(False)
@@ -114,6 +121,3 @@
     mode = ad.droid.getPrivateDnsMode()
     host = ad.droid.getPrivateDnsSpecifier()
     ad.log.info("DNS mode is %s and DNS server is %s" % (mode, host))
-    asserts.assert_true(dns_mode == mode and host == hostname,
-                        "Failed to set DNS mode to %s and DNS to %s" % \
-                        (dns_mode, hostname))
diff --git a/acts_tests/acts_contrib/test_utils/net/ui_utils.py b/acts_tests/acts_contrib/test_utils/net/ui_utils.py
index eb3bc14..143719e 100644
--- a/acts_tests/acts_contrib/test_utils/net/ui_utils.py
+++ b/acts_tests/acts_contrib/test_utils/net/ui_utils.py
@@ -129,16 +129,23 @@
         long_clickable
         password
         selected
+        A special key/value: matching_node key is used to identify If more than one nodes have the same key/value,
+            the matching_node stands for which matching node should be fetched.
 
   Returns:
     XML node of the UI element or None if not found.
   """
   nodes = screen_dump_xml.getElementsByTagName('node')
+  matching_node = kwargs.pop('matching_node', 1)
+  count = 1
   for node in nodes:
     if match_node(node, **kwargs):
-      logging.debug('Found a node matching conditions: %s',
-                    get_key_value_pair_strings(kwargs))
-      return node
+      if count == matching_node:
+        logging.debug('Found a node matching conditions: %s',
+                      get_key_value_pair_strings(kwargs))
+        return node
+      count += 1
+  return None
 
 
 def wait_and_get_xml_node(device, timeout, child=None, sibling=None, **kwargs):
@@ -244,3 +251,27 @@
     args = 'input swipe %s %s %s %s %s' % \
         (str(x), str(y), str(x), str(y), str(duration_ms))
   device.adb.shell(args)
+
+def wait_and_input_text(device, input_text, duration_ms=None, **kwargs):
+  """Wait for a UI element text field that can accept text entry.
+
+  This function located a UI element using wait_and_click. Once the element is
+  clicked, the text is input into the text field.
+
+  Args:
+    device: AndroidDevice, Mobly's Android controller object.
+    input_text: Text string to be entered in to the text field.
+    duration_ms: duration in milliseconds.
+    **kwargs: A set of `key=value` parameters that identifies a UI element.
+  """
+  wait_and_click(device, duration_ms, **kwargs)
+  # Replace special characters.
+  # The command "input text <string>" requires special treatment for
+  # characters ' ' and '&'.  They need to be escaped. for example:
+  #    "hello world!!&" needs to transform to "hello\ world!!\&"
+  special_chars = ' &'
+  for c in special_chars:
+    input_text = input_text.replace(c, '\\%s' % c)
+  input_text = "'" + input_text + "'"
+  args = 'input text %s' % input_text
+  device.adb.shell(args)
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
index 1481e95..6e62263 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
@@ -464,6 +464,7 @@
                                 measure_after_seconds=self.mon_info.offset,
                                 hz=self.mon_info.freq)
         self.power_monitor.measure(measurement_args=measurement_args,
+                                   measurement_name=self.test_name,
                                    start_time=device_to_host_offset,
                                    monsoon_output_path=data_path)
         self.power_monitor.release_resources()
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py
index 36ab5fe..37b9e56 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py
@@ -43,7 +43,8 @@
 
     def setup_class(self):
         super().setup_class()
-        req_params = ['customjsfile', 'maskfile']
+        req_params = ['customjsfile', 'maskfile', 'dpooff_nv_dict',
+                      'dpoon_nv_dict', 'mdsapp', 'modemparfile']
         self.unpack_userparams(req_params)
 
     def collect_power_data(self):
diff --git a/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py b/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
index e289073..70b38fb 100644
--- a/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
@@ -24,19 +24,18 @@
 import time
 
 from acts import asserts
-from acts import logger as acts_logger
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
-from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
 from acts.keys import Config
 from acts import records
 from acts import utils
 
-from acts_contrib.test_utils.tel.tel_subscription_utils import \
-    initial_set_up_for_subid_infomation
-from acts_contrib.test_utils.tel.tel_subscription_utils import \
-    set_default_sub_for_all_services
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_loggers
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_logger
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_sdm_logger
+from acts_contrib.test_utils.tel.tel_subscription_utils import initial_set_up_for_subid_information
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_default_sub_for_all_services
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
 from acts_contrib.test_utils.tel.tel_test_utils import build_id_override
 from acts_contrib.test_utils.tel.tel_test_utils import disable_qxdm_logger
@@ -47,7 +46,6 @@
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
 from acts_contrib.test_utils.tel.tel_test_utils import flash_radio
 from acts_contrib.test_utils.tel.tel_test_utils import force_connectivity_metrics_upload
-from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 from acts_contrib.test_utils.tel.tel_test_utils import get_screen_shot_log
 from acts_contrib.test_utils.tel.tel_test_utils import get_sim_state
 from acts_contrib.test_utils.tel.tel_test_utils import get_tcpdump_log
@@ -63,12 +61,8 @@
 from acts_contrib.test_utils.tel.tel_test_utils import set_qxdm_logger_command
 from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_logger
 from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import start_sdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import start_sdm_logger
 from acts_contrib.test_utils.tel.tel_test_utils import start_tcpdumps
 from acts_contrib.test_utils.tel.tel_test_utils import stop_qxdm_logger
-from acts_contrib.test_utils.tel.tel_test_utils import stop_sdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import stop_sdm_logger
 from acts_contrib.test_utils.tel.tel_test_utils import stop_tcpdumps
 from acts_contrib.test_utils.tel.tel_test_utils import synchronize_device_time
 from acts_contrib.test_utils.tel.tel_test_utils import unlock_sim
@@ -80,7 +74,6 @@
 from acts_contrib.test_utils.tel.tel_test_utils import install_googlefi_apk
 from acts_contrib.test_utils.tel.tel_test_utils import activate_google_fi_account
 from acts_contrib.test_utils.tel.tel_test_utils import check_google_fi_activated
-from acts_contrib.test_utils.tel.tel_test_utils import check_fi_apk_installed
 from acts_contrib.test_utils.tel.tel_test_utils import phone_switch_to_msim_mode
 from acts_contrib.test_utils.tel.tel_test_utils import activate_esim_using_suw
 from acts_contrib.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
@@ -365,7 +358,7 @@
         activate_wfc_on_device(self.log, ad)
 
         # Sub ID setup
-        initial_set_up_for_subid_infomation(self.log, ad)
+        initial_set_up_for_subid_information(self.log, ad)
 
 
         #try:
@@ -484,7 +477,8 @@
             start_qxdm_loggers(self.log, self.android_devices, self.begin_time)
         if getattr(self, "sdm_log", False):
             start_sdm_loggers(self.log, self.android_devices)
-        if getattr(self, "tcpdump_log", False) or "wfc" in self.test_name:
+        if getattr(self, "tcpdump_log", False) or "wfc" in self.test_name or (
+            "iwlan" in self.test_name):
             mask = getattr(self, "tcpdump_mask", "all")
             interface = getattr(self, "tcpdump_interface", "wlan0")
             start_tcpdumps(
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_defines.py b/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
index 69be9a7..94d03f1 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
@@ -698,6 +698,7 @@
 CARRIER_TEST_CONF_XML_PATH = "/data/user_de/0/com.android.phone/files/"
 MAIN_ACTIVITY = "android.intent.action.MAIN"
 CBR_PACKAGE = "com.google.android.cellbroadcastreceiver"
+SYSUI_PACKAGE = "com.android.systemui"
 CBR_ACTIVITY = "com.android.cellbroadcastreceiver.CellBroadcastSettings"
 CBR_TEST_APK = "com.android.cellbroadcastreceiver.tests"
 MCC_MNC = "mccmnc"
@@ -710,7 +711,7 @@
 DEFAULT_SOUND_TIME = 16
 DEFAULT_VIBRATION_TIME = 10
 DEFAULT_OFFSET = 1
-EXIT_ALERT_LIST = ["Got It", "OK", "Hide", "TO CLOSE"]
+EXIT_ALERT_LIST = ["Got it", "OK", "Hide", "TO CLOSE", "Yes"]
 CMD_DND_OFF = "cmd notification set_dnd off"
 CMD_DND_ON = "cmd notification set_dnd on"
 DUMPSYS_VIBRATION = "dumpsys vibrator_manager | grep -i  com.google.android.cellbroadcastreceiver | tail -1"
@@ -723,9 +724,12 @@
 AUSTRALIA = "australia"
 BRAZIL = "brazil"
 CANADA = "canada"
-CHILE = "chile"
+CHILE_ENTEL = "chile_entel"
+CHILE_TELEFONICA = "chile_telefonica"
 COLUMBIA = "columbia"
-ECUADOR = "ecuador"
+ECUADOR_TELEFONICA = "ecuador_telefonica"
+ECUADOR_CLARO = "ecuador_claro"
+ELSALVADOR_TELEFONICA = "elsalvador_telefonica"
 ESTONIA = "estonia"
 FRANCE = "france"
 GREECE = "greece"
@@ -737,10 +741,12 @@
 KOREA = "korea"
 LATVIA = "latvia"
 LITHUANIA = "lithuania"
+MEXICO_TELEFONICA = "mexico_telefonica"
 NETHERLANDS = "netherlands"
 NEWZEALAND = "newzealand"
 OMAN = "oman"
-PERU = "peru"
+PERU_ENTEL = "peru_entel"
+PERU_TELEFONICA = "peru_telefonica"
 PUERTORICO = "puertorico"
 ROMANIA = "romania"
 SAUDIARABIA = "saudiarabia"
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_dsds_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_dsds_utils.py
index 58a3c10..56ad4b3 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_dsds_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_dsds_utils.py
@@ -21,7 +21,6 @@
 from acts import signals
 from acts.utils import rand_ascii_str
 from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
-from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
@@ -54,7 +53,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import set_call_forwarding_by_mmi
 from acts_contrib.test_utils.tel.tel_test_utils import set_call_waiting
 from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify_for_subscription
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import start_youtube_video
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_wfc_for_subscription
@@ -75,11 +74,10 @@
 from acts_contrib.test_utils.tel.tel_voice_conf_utils import _three_phone_call_mo_add_mt
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
-tel_logger = TelephonyMetricLogger.for_test_case()
-
 
 def dsds_voice_call_test(
         log,
+        tel_logger,
         ads,
         mo_slot,
         mt_slot,
@@ -110,6 +108,7 @@
 
     Args:
         log: logger object
+        tel_logger: logger object for telephony proto
         ads: list of android devices
         mo_slot: Slot making MO call (0 or 1)
         mt_slot: Slot receiving MT call (0 or 1)
@@ -471,6 +470,7 @@
 
 def dds_switch_during_data_transfer_test(
         log,
+        tel_logger,
         ads,
         nw_rat=["volte", "volte"],
         call_slot=0,
@@ -496,6 +496,7 @@
 
     Args:
         log: logger object
+        tel_logger: logger object for telephony proto
         ads: list of android devices
         nw_rat: RAT for both slots of the primary device
         call_slot: Slot for making voice call
@@ -714,6 +715,7 @@
 
 def enable_slot_after_voice_call_test(
         log,
+        tel_logger,
         ads,
         mo_slot,
         mt_slot,
@@ -736,6 +738,7 @@
 
     Args:
         log: logger object
+        tel_logger: logger object for telephony proto
         ads: list of android devices
         mo_slot: Slot making MO call (0 or 1)
         mt_slot: Slot receiving MT call (0 or 1)
@@ -1240,6 +1243,7 @@
 
 def msim_call_forwarding(
         log,
+        tel_logger,
         ads,
         caller_slot,
         callee_slot,
@@ -1263,6 +1267,9 @@
         to 3rd device.
 
     Args:
+        log: logger object
+        tel_logger: logger object for telephony proto
+        ads: list of android devices
         caller_slot: Slot of 2nd device making MO call (0 or 1)
         callee_slot: Slot of primary device receiving and forwarding MT call
                         (0 or 1)
@@ -1510,6 +1517,7 @@
 
 def msim_call_voice_conf(
         log,
+        tel_logger,
         ads,
         host_slot,
         p1_slot,
@@ -1532,6 +1540,9 @@
     6. Merge calls.
 
     Args:
+        log: logger object
+        tel_logger: logger object for telephony proto
+        ads: list of android devices
         host_slot: Slot on the primary device to host the comference call.
         0 or 1 (0 for pSIM or 1 for eSIM)
         p1_slot: Slot on the participant device for the call
@@ -1739,6 +1750,7 @@
 
 def msim_volte_wfc_call_forwarding(
         log,
+        tel_logger,
         ads,
         callee_slot,
         dds_slot,
@@ -1765,6 +1777,9 @@
         forwarded to 3rd device.
 
     Args:
+        log: logger object
+        tel_logger: logger object for telephony proto
+        ads: list of android devices
         callee_slot: Slot of primary device receiving and forwarding MT call
                         (0 or 1)
         dds_slot: Preferred data slot
@@ -1934,6 +1949,7 @@
 
 def msim_volte_wfc_call_voice_conf(
         log,
+        tel_logger,
         ads,
         host_slot,
         dds_slot,
@@ -1960,6 +1976,9 @@
     8. Merge calls.
 
     Args:
+        log: logger object
+        tel_logger: logger object for telephony proto
+        ads: list of android devices
         host_slot: Slot on the primary device to host the comference call.
                     0 or 1 (0 for pSIM or 1 for eSIM)call
         dds_slot: Preferred data slot
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_logging_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_logging_utils.py
new file mode 100644
index 0000000..b5423d8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_logging_utils.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2021 - Google
+#
+#   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 time
+
+from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
+from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
+
+def check_if_tensor_platform(ad):
+    """Check if current platform belongs to the Tensor platform
+
+    Args:
+        ad: Android object
+
+    Returns:
+        True if current platform belongs to the Tensor platform. Otherwise False.
+    """
+    result = ad.adb.getprop("ro.boot.hardware.platform")
+    if re.search('^gs', result, re.I):
+        return True
+    return False
+
+def start_pixellogger_always_on_logging(ad):
+    """Start always-on logging of Pixellogger for both Qualcomm and Tensor
+    platform.
+
+    Args:
+        ad: Android object
+
+    Returns:
+        True if the property is set correctly. Otherwise False.
+    """
+    setattr(ad, 'enable_always_on_modem_logger', True)
+    if check_if_tensor_platform(ad):
+        key = "persist.vendor.sys.modem.logging.enable"
+    else:
+        key = "persist.vendor.sys.modem.diag.mdlog"
+
+    if ad.adb.getprop(key) == "false":
+        ad.adb.shell("setprop persist.vendor.sys.modem.logging.enable true")
+        time.sleep(5)
+        if ad.adb.getprop(key) == "true":
+            return True
+        else:
+            return False
+    else:
+        return True
+
+def start_sdm_logger(ad):
+    """Start SDM logger."""
+    if not getattr(ad, "sdm_log", True): return
+    # Delete existing SDM logs which were created 15 mins prior
+    ad.sdm_log_path = DEFAULT_SDM_LOG_PATH
+    file_count = ad.adb.shell(
+        "find %s -type f -iname sbuff_[0-9]*.sdm* | wc -l" % ad.sdm_log_path)
+    if int(file_count) > 3:
+        seconds = 15 * 60
+        # Remove sdm logs modified more than specified seconds ago
+        ad.adb.shell(
+            "find %s -type f -iname sbuff_[0-9]*.sdm* -not -mtime -%ss -delete" %
+            (ad.sdm_log_path, seconds))
+    # Disable any modem logging already running
+    if not getattr(ad, "enable_always_on_modem_logger", False):
+        ad.adb.shell("setprop persist.vendor.sys.modem.logging.enable false")
+    # start logging
+    cmd = "setprop vendor.sys.modem.logging.enable true"
+    ad.log.debug("start sdm logging")
+    ad.adb.shell(cmd, ignore_status=True)
+    time.sleep(5)
+
+def stop_sdm_logger(ad):
+    """Stop SDM logger."""
+    cmd = "setprop vendor.sys.modem.logging.enable false"
+    ad.log.debug("stop sdm logging")
+    ad.adb.shell(cmd, ignore_status=True)
+    time.sleep(5)
+
+def start_sdm_loggers(log, ads):
+    tasks = [(start_sdm_logger, [ad]) for ad in ads
+             if getattr(ad, "sdm_log", True)]
+    if tasks: run_multithread_func(log, tasks)
+
+def stop_sdm_loggers(log, ads):
+    tasks = [(stop_sdm_logger, [ad]) for ad in ads]
+    run_multithread_func(log, tasks)
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_message_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_message_utils.py
index 2e52767..0cfe8d5 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_message_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_message_utils.py
@@ -14,20 +14,51 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+import re
 import time
+from queue import Empty
+from acts import signals
 from acts.utils import rand_ascii_str
+from acts_contrib.test_utils.tel.tel_defines import EventCallStateChanged
+from acts_contrib.test_utils.tel.tel_defines import EventSmsDeliverFailure
+from acts_contrib.test_utils.tel.tel_defines import EventSmsDeliverSuccess
+from acts_contrib.test_utils.tel.tel_defines import EventSmsReceived
+from acts_contrib.test_utils.tel.tel_defines import EventSmsSentFailure
+from acts_contrib.test_utils.tel.tel_defines import EventSmsSentSuccess
+from acts_contrib.test_utils.tel.tel_defines import INCALL_UI_DISPLAY_BACKGROUND
+from acts_contrib.test_utils.tel.tel_defines import INCALL_UI_DISPLAY_FOREGROUND
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_SENT_SUCCESS_IN_COLLISION
 from acts_contrib.test_utils.tel.tel_defines import SMS_OVER_WIFI_PROVIDERS
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_DISABLED
+from acts_contrib.test_utils.tel.tel_test_utils import CallResult
+from acts_contrib.test_utils.tel.tel_test_utils import TelResultWrapper
 from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
+from acts_contrib.test_utils.tel.tel_test_utils import check_phone_number_match
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_wifi_connected
+from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
 from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_test_utils import is_phone_in_call
+from acts_contrib.test_utils.tel.tel_test_utils import last_call_drop_reason
+from acts_contrib.test_utils.tel.tel_test_utils import log_messaging_screen_shot
+from acts_contrib.test_utils.tel.tel_test_utils import mms_receive_verify_after_call_hangup
 from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import mms_receive_verify_after_call_hangup
+from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import wait_and_answer_call_for_subscription
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_in_call_active
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_call_end
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_call_offhook_for_subscription
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_incoming_message_sub_id
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_message_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_on_same_network_of_host_ad
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_message
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_on_rat
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
 from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
@@ -212,4 +243,1284 @@
             ad_mo.log.info("Failed to hang up call!")
             result = False
 
-    return result
\ No newline at end of file
+    return result
+
+def sms_send_receive_verify(log,
+                            ad_tx,
+                            ad_rx,
+                            array_message,
+                            max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
+                            expected_result=True,
+                            slot_id_rx=None):
+    """Send SMS, receive SMS, and verify content and sender's number.
+
+        Send (several) SMS from droid_tx to droid_rx.
+        Verify SMS is sent, delivered and received.
+        Verify received content and sender's number are correct.
+
+    Args:
+        log: Log object.
+        ad_tx: Sender's Android Device Object
+        ad_rx: Receiver's Android Device Object
+        array_message: the array of message to send/receive
+        slot_id_rx: the slot on the Receiver's android device (0/1)
+    """
+    subid_tx = get_outgoing_message_sub_id(ad_tx)
+    if slot_id_rx is None:
+        subid_rx = get_incoming_message_sub_id(ad_rx)
+    else:
+        subid_rx = get_subid_from_slot_index(log, ad_rx, slot_id_rx)
+
+    result = sms_send_receive_verify_for_subscription(
+        log, ad_tx, ad_rx, subid_tx, subid_rx, array_message, max_wait_time)
+    if result != expected_result:
+        log_messaging_screen_shot(ad_tx, test_name="sms_tx")
+        log_messaging_screen_shot(ad_rx, test_name="sms_rx")
+    return result == expected_result
+
+def sms_send_receive_verify_for_subscription(
+        log,
+        ad_tx,
+        ad_rx,
+        subid_tx,
+        subid_rx,
+        array_message,
+        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
+    """Send SMS, receive SMS, and verify content and sender's number.
+
+        Send (several) SMS from droid_tx to droid_rx.
+        Verify SMS is sent, delivered and received.
+        Verify received content and sender's number are correct.
+
+    Args:
+        log: Log object.
+        ad_tx: Sender's Android Device Object..
+        ad_rx: Receiver's Android Device Object.
+        subid_tx: Sender's subscription ID to be used for SMS
+        subid_rx: Receiver's subscription ID to be used for SMS
+        array_message: the array of message to send/receive
+    """
+    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
+    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
+
+    for ad in (ad_tx, ad_rx):
+        if not getattr(ad, "messaging_droid", None):
+            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+            ad.messaging_ed.start()
+        else:
+            try:
+                if not ad.messaging_droid.is_live:
+                    ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                    ad.messaging_ed.start()
+                else:
+                    ad.messaging_ed.clear_all_events()
+                ad.messaging_droid.logI(
+                    "Start sms_send_receive_verify_for_subscription test")
+            except Exception:
+                ad.log.info("Create new sl4a session for messaging")
+                ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                ad.messaging_ed.start()
+
+    for text in array_message:
+        length = len(text)
+        ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
+                       phonenumber_tx, phonenumber_rx, length, text)
+        try:
+            ad_rx.messaging_ed.clear_events(EventSmsReceived)
+            ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
+            ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
+            ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
+            time.sleep(1)  #sleep 100ms after starting event tracking
+            ad_tx.messaging_droid.logI("Sending SMS of length %s" % length)
+            ad_rx.messaging_droid.logI("Expecting SMS of length %s" % length)
+            ad_tx.messaging_droid.smsSendTextMessage(phonenumber_rx, text,
+                                                     True)
+            try:
+                events = ad_tx.messaging_ed.pop_events(
+                    "(%s|%s|%s|%s)" %
+                    (EventSmsSentSuccess, EventSmsSentFailure,
+                     EventSmsDeliverSuccess,
+                     EventSmsDeliverFailure), max_wait_time)
+                for event in events:
+                    ad_tx.log.info("Got event %s", event["name"])
+                    if event["name"] == EventSmsSentFailure or event["name"] == EventSmsDeliverFailure:
+                        if event.get("data") and event["data"].get("Reason"):
+                            ad_tx.log.error("%s with reason: %s",
+                                            event["name"],
+                                            event["data"]["Reason"])
+                        return False
+                    elif event["name"] == EventSmsSentSuccess or event["name"] == EventSmsDeliverSuccess:
+                        break
+            except Empty:
+                ad_tx.log.error("No %s or %s event for SMS of length %s.",
+                                EventSmsSentSuccess, EventSmsSentFailure,
+                                length)
+                return False
+
+            if not wait_for_matching_sms(
+                    log,
+                    ad_rx,
+                    phonenumber_tx,
+                    text,
+                    max_wait_time,
+                    allow_multi_part_long_sms=True):
+                ad_rx.log.error("No matching received SMS of length %s.",
+                                length)
+                return False
+        except Exception as e:
+            log.error("Exception error %s", e)
+            raise
+        finally:
+            ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
+    return True
+
+def sms_in_collision_send_receive_verify(
+        log,
+        ad_rx,
+        ad_rx2,
+        ad_tx,
+        ad_tx2,
+        array_message,
+        array_message2,
+        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+    """Send 2 SMS', receive both SMS', and verify content and sender's number of
+       each SMS.
+
+        Send 2 SMS'. One from ad_tx to ad_rx and the other from ad_tx2 to ad_rx2.
+        When ad_rx is identical to ad_rx2, the scenario of SMS' in collision can
+        be tested.
+        Verify both SMS' are sent, delivered and received.
+        Verify received content and sender's number of each SMS is correct.
+
+    Args:
+        log: Log object.
+        ad_tx: Sender's Android Device Object..
+        ad_rx: Receiver's Android Device Object.
+        ad_tx2: 2nd sender's Android Device Object..
+        ad_rx2: 2nd receiver's Android Device Object.
+        array_message: the array of message to send/receive from ad_tx to ad_rx
+        array_message2: the array of message to send/receive from ad_tx2 to
+        ad_rx2
+        max_wait_time: Max time to wait for reception of SMS
+    """
+    rx_sub_id = get_outgoing_message_sub_id(ad_rx)
+    rx2_sub_id = get_outgoing_message_sub_id(ad_rx2)
+
+    _, tx_sub_id, _ = get_subid_on_same_network_of_host_ad(
+        [ad_rx, ad_tx, ad_tx2],
+        host_sub_id=rx_sub_id)
+    set_subid_for_message(ad_tx, tx_sub_id)
+
+    _, _, tx2_sub_id = get_subid_on_same_network_of_host_ad(
+        [ad_rx2, ad_tx, ad_tx2],
+        host_sub_id=rx2_sub_id)
+    set_subid_for_message(ad_tx2, tx2_sub_id)
+
+    if not sms_in_collision_send_receive_verify_for_subscription(
+        log,
+        ad_tx,
+        ad_tx2,
+        ad_rx,
+        ad_rx2,
+        tx_sub_id,
+        tx2_sub_id,
+        rx_sub_id,
+        rx_sub_id,
+        array_message,
+        array_message2,
+        max_wait_time):
+        log_messaging_screen_shot(
+            ad_rx, test_name="sms rx subid: %s" % rx_sub_id)
+        log_messaging_screen_shot(
+            ad_rx2, test_name="sms rx2 subid: %s" % rx2_sub_id)
+        log_messaging_screen_shot(
+            ad_tx, test_name="sms tx subid: %s" % tx_sub_id)
+        log_messaging_screen_shot(
+            ad_tx2, test_name="sms tx subid: %s" % tx2_sub_id)
+        return False
+    return True
+
+def sms_in_collision_send_receive_verify_for_subscription(
+        log,
+        ad_tx,
+        ad_tx2,
+        ad_rx,
+        ad_rx2,
+        subid_tx,
+        subid_tx2,
+        subid_rx,
+        subid_rx2,
+        array_message,
+        array_message2,
+        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+    """Send 2 SMS', receive both SMS', and verify content and sender's number of
+       each SMS.
+
+        Send 2 SMS'. One from ad_tx to ad_rx and the other from ad_tx2 to ad_rx2.
+        When ad_rx is identical to ad_rx2, the scenario of SMS' in collision can
+        be tested.
+        Verify both SMS' are sent, delivered and received.
+        Verify received content and sender's number of each SMS is correct.
+
+    Args:
+        log: Log object.
+        ad_tx: Sender's Android Device Object..
+        ad_rx: Receiver's Android Device Object.
+        ad_tx2: 2nd sender's Android Device Object..
+        ad_rx2: 2nd receiver's Android Device Object.
+        subid_tx: Sub ID of ad_tx as default Sub ID for outgoing SMS
+        subid_tx2: Sub ID of ad_tx2 as default Sub ID for outgoing SMS
+        subid_rx: Sub ID of ad_rx as default Sub ID for incoming SMS
+        subid_rx2: Sub ID of ad_rx2 as default Sub ID for incoming SMS
+        array_message: the array of message to send/receive from ad_tx to ad_rx
+        array_message2: the array of message to send/receive from ad_tx2 to
+        ad_rx2
+        max_wait_time: Max time to wait for reception of SMS
+    """
+
+    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
+    phonenumber_tx2 = ad_tx2.telephony['subscription'][subid_tx2]['phone_num']
+    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
+    phonenumber_rx2 = ad_rx2.telephony['subscription'][subid_rx2]['phone_num']
+
+    for ad in (ad_tx, ad_tx2, ad_rx, ad_rx2):
+        ad.send_keycode("BACK")
+        if not getattr(ad, "messaging_droid", None):
+            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+            ad.messaging_ed.start()
+        else:
+            try:
+                if not ad.messaging_droid.is_live:
+                    ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                    ad.messaging_ed.start()
+                else:
+                    ad.messaging_ed.clear_all_events()
+                ad.messaging_droid.logI(
+                    "Start sms_send_receive_verify_for_subscription test")
+            except Exception:
+                ad.log.info("Create new sl4a session for messaging")
+                ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                ad.messaging_ed.start()
+
+    for text, text2 in zip(array_message, array_message2):
+        length = len(text)
+        length2 = len(text2)
+        ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
+                       phonenumber_tx, phonenumber_rx, length, text)
+        ad_tx2.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
+                       phonenumber_tx2, phonenumber_rx2, length2, text2)
+
+        try:
+            ad_rx.messaging_ed.clear_events(EventSmsReceived)
+            ad_rx2.messaging_ed.clear_events(EventSmsReceived)
+            ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
+            ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
+            ad_tx2.messaging_ed.clear_events(EventSmsSentSuccess)
+            ad_tx2.messaging_ed.clear_events(EventSmsSentFailure)
+            ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
+            if ad_rx2 != ad_rx:
+                ad_rx2.messaging_droid.smsStartTrackingIncomingSmsMessage()
+            time.sleep(1)
+            ad_tx.messaging_droid.logI("Sending SMS of length %s" % length)
+            ad_tx2.messaging_droid.logI("Sending SMS of length %s" % length2)
+            ad_rx.messaging_droid.logI(
+                "Expecting SMS of length %s from %s" % (length, ad_tx.serial))
+            ad_rx2.messaging_droid.logI(
+                "Expecting SMS of length %s from %s" % (length2, ad_tx2.serial))
+
+            tasks = [
+                (ad_tx.messaging_droid.smsSendTextMessage,
+                (phonenumber_rx, text, True)),
+                (ad_tx2.messaging_droid.smsSendTextMessage,
+                (phonenumber_rx2, text2, True))]
+            multithread_func(log, tasks)
+            try:
+                tasks = [
+                    (ad_tx.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
+                        EventSmsSentSuccess,
+                        EventSmsSentFailure,
+                        EventSmsDeliverSuccess,
+                        EventSmsDeliverFailure), max_wait_time)),
+                    (ad_tx2.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
+                        EventSmsSentSuccess,
+                        EventSmsSentFailure,
+                        EventSmsDeliverSuccess,
+                        EventSmsDeliverFailure), max_wait_time))
+                ]
+                results = run_multithread_func(log, tasks)
+                res = True
+                _ad = ad_tx
+                for ad, events in [(ad_tx, results[0]),(ad_tx2, results[1])]:
+                    _ad = ad
+                    for event in events:
+                        ad.log.info("Got event %s", event["name"])
+                        if event["name"] == EventSmsSentFailure or \
+                            event["name"] == EventSmsDeliverFailure:
+                            if event.get("data") and event["data"].get("Reason"):
+                                ad.log.error("%s with reason: %s",
+                                                event["name"],
+                                                event["data"]["Reason"])
+                            res = False
+                        elif event["name"] == EventSmsSentSuccess or \
+                            event["name"] == EventSmsDeliverSuccess:
+                            break
+                if not res:
+                    return False
+            except Empty:
+                _ad.log.error("No %s or %s event for SMS of length %s.",
+                                EventSmsSentSuccess, EventSmsSentFailure,
+                                length)
+                return False
+            if ad_rx == ad_rx2:
+                if not wait_for_matching_mt_sms_in_collision(
+                    log,
+                    ad_rx,
+                    phonenumber_tx,
+                    phonenumber_tx2,
+                    text,
+                    text2,
+                    max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+
+                    ad_rx.log.error(
+                        "No matching received SMS of length %s from %s.",
+                        length,
+                        ad_rx.serial)
+                    return False
+            else:
+                if not wait_for_matching_mt_sms_in_collision_with_mo_sms(
+                    log,
+                    ad_rx,
+                    ad_rx2,
+                    phonenumber_tx,
+                    phonenumber_tx2,
+                    text,
+                    text2,
+                    max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+                    return False
+        except Exception as e:
+            log.error("Exception error %s", e)
+            raise
+        finally:
+            ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
+            ad_rx2.messaging_droid.smsStopTrackingIncomingSmsMessage()
+    return True
+
+def sms_rx_power_off_multiple_send_receive_verify(
+        log,
+        ad_rx,
+        ad_tx,
+        ad_tx2,
+        array_message_length,
+        array_message2_length,
+        num_array_message,
+        num_array_message2,
+        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+
+    rx_sub_id = get_outgoing_message_sub_id(ad_rx)
+
+    _, tx_sub_id, _ = get_subid_on_same_network_of_host_ad(
+        [ad_rx, ad_tx, ad_tx2],
+        host_sub_id=rx_sub_id)
+    set_subid_for_message(ad_tx, tx_sub_id)
+
+    _, _, tx2_sub_id = get_subid_on_same_network_of_host_ad(
+        [ad_rx, ad_tx, ad_tx2],
+        host_sub_id=rx_sub_id)
+    set_subid_for_message(ad_tx2, tx2_sub_id)
+
+    if not sms_rx_power_off_multiple_send_receive_verify_for_subscription(
+        log,
+        ad_tx,
+        ad_tx2,
+        ad_rx,
+        tx_sub_id,
+        tx2_sub_id,
+        rx_sub_id,
+        rx_sub_id,
+        array_message_length,
+        array_message2_length,
+        num_array_message,
+        num_array_message2):
+        log_messaging_screen_shot(
+            ad_rx, test_name="sms rx subid: %s" % rx_sub_id)
+        log_messaging_screen_shot(
+            ad_tx, test_name="sms tx subid: %s" % tx_sub_id)
+        log_messaging_screen_shot(
+            ad_tx2, test_name="sms tx subid: %s" % tx2_sub_id)
+        return False
+    return True
+
+def sms_rx_power_off_multiple_send_receive_verify_for_subscription(
+        log,
+        ad_tx,
+        ad_tx2,
+        ad_rx,
+        subid_tx,
+        subid_tx2,
+        subid_rx,
+        subid_rx2,
+        array_message_length,
+        array_message2_length,
+        num_array_message,
+        num_array_message2,
+        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+
+    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
+    phonenumber_tx2 = ad_tx2.telephony['subscription'][subid_tx2]['phone_num']
+    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
+    phonenumber_rx2 = ad_rx.telephony['subscription'][subid_rx2]['phone_num']
+
+    if not toggle_airplane_mode(log, ad_rx, True):
+        ad_rx.log.error("Failed to enable Airplane Mode")
+        return False
+    ad_rx.stop_services()
+    ad_rx.log.info("Rebooting......")
+    ad_rx.adb.reboot()
+
+    message_dict = {phonenumber_tx: [], phonenumber_tx2: []}
+    for index in range(max(num_array_message, num_array_message2)):
+        array_message = [rand_ascii_str(array_message_length)]
+        array_message2 = [rand_ascii_str(array_message2_length)]
+        for text, text2 in zip(array_message, array_message2):
+            message_dict[phonenumber_tx].append(text)
+            message_dict[phonenumber_tx2].append(text2)
+            length = len(text)
+            length2 = len(text2)
+
+            ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
+                           phonenumber_tx, phonenumber_rx, length, text)
+            ad_tx2.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
+                           phonenumber_tx2, phonenumber_rx2, length2, text2)
+
+            try:
+                for ad in (ad_tx, ad_tx2):
+                    ad.send_keycode("BACK")
+                    if not getattr(ad, "messaging_droid", None):
+                        ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                        ad.messaging_ed.start()
+                    else:
+                        try:
+                            if not ad.messaging_droid.is_live:
+                                ad.messaging_droid, ad.messaging_ed = \
+                                    ad.get_droid()
+                                ad.messaging_ed.start()
+                            else:
+                                ad.messaging_ed.clear_all_events()
+                            ad.messaging_droid.logI(
+                                "Start sms_send_receive_verify_for_subscription"
+                                " test")
+                        except Exception:
+                            ad.log.info("Create new sl4a session for messaging")
+                            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                            ad.messaging_ed.start()
+
+                ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
+                ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
+                ad_tx2.messaging_ed.clear_events(EventSmsSentSuccess)
+                ad_tx2.messaging_ed.clear_events(EventSmsSentFailure)
+
+                if index < num_array_message and index < num_array_message2:
+                    ad_tx.messaging_droid.logI(
+                        "Sending SMS of length %s" % length)
+                    ad_tx2.messaging_droid.logI(
+                        "Sending SMS of length %s" % length2)
+                    tasks = [
+                        (ad_tx.messaging_droid.smsSendTextMessage,
+                        (phonenumber_rx, text, True)),
+                        (ad_tx2.messaging_droid.smsSendTextMessage,
+                        (phonenumber_rx2, text2, True))]
+                    multithread_func(log, tasks)
+                else:
+                    if index < num_array_message:
+                        ad_tx.messaging_droid.logI(
+                            "Sending SMS of length %s" % length)
+                        ad_tx.messaging_droid.smsSendTextMessage(
+                            phonenumber_rx, text, True)
+                    if index < num_array_message2:
+                        ad_tx2.messaging_droid.logI(
+                            "Sending SMS of length %s" % length2)
+                        ad_tx2.messaging_droid.smsSendTextMessage(
+                            phonenumber_rx2, text2, True)
+
+                try:
+                    if index < num_array_message and index < num_array_message2:
+                        tasks = [
+                            (ad_tx.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
+                                EventSmsSentSuccess,
+                                EventSmsSentFailure,
+                                EventSmsDeliverSuccess,
+                                EventSmsDeliverFailure),
+                                max_wait_time)),
+                            (ad_tx2.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
+                                EventSmsSentSuccess,
+                                EventSmsSentFailure,
+                                EventSmsDeliverSuccess,
+                                EventSmsDeliverFailure),
+                                max_wait_time))
+                        ]
+                        results = run_multithread_func(log, tasks)
+                        res = True
+                        _ad = ad_tx
+                        for ad, events in [
+                            (ad_tx, results[0]), (ad_tx2, results[1])]:
+                            _ad = ad
+                            for event in events:
+                                ad.log.info("Got event %s", event["name"])
+                                if event["name"] == EventSmsSentFailure or \
+                                    event["name"] == EventSmsDeliverFailure:
+                                    if event.get("data") and \
+                                        event["data"].get("Reason"):
+                                        ad.log.error("%s with reason: %s",
+                                                        event["name"],
+                                                        event["data"]["Reason"])
+                                    res = False
+                                elif event["name"] == EventSmsSentSuccess or \
+                                    event["name"] == EventSmsDeliverSuccess:
+                                    break
+                        if not res:
+                            return False
+                    else:
+                        if index < num_array_message:
+                            result = ad_tx.messaging_ed.pop_events(
+                                "(%s|%s|%s|%s)" % (
+                                    EventSmsSentSuccess,
+                                    EventSmsSentFailure,
+                                    EventSmsDeliverSuccess,
+                                    EventSmsDeliverFailure),
+                                max_wait_time)
+                            res = True
+                            _ad = ad_tx
+                            for ad, events in [(ad_tx, result)]:
+                                _ad = ad
+                                for event in events:
+                                    ad.log.info("Got event %s", event["name"])
+                                    if event["name"] == EventSmsSentFailure or \
+                                        event["name"] == EventSmsDeliverFailure:
+                                        if event.get("data") and \
+                                            event["data"].get("Reason"):
+                                            ad.log.error(
+                                                "%s with reason: %s",
+                                                event["name"],
+                                                event["data"]["Reason"])
+                                        res = False
+                                    elif event["name"] == EventSmsSentSuccess \
+                                        or event["name"] == EventSmsDeliverSuccess:
+                                        break
+                            if not res:
+                                return False
+                        if index < num_array_message2:
+                            result = ad_tx2.messaging_ed.pop_events(
+                                "(%s|%s|%s|%s)" % (
+                                    EventSmsSentSuccess,
+                                    EventSmsSentFailure,
+                                    EventSmsDeliverSuccess,
+                                    EventSmsDeliverFailure),
+                                max_wait_time)
+                            res = True
+                            _ad = ad_tx2
+                            for ad, events in [(ad_tx2, result)]:
+                                _ad = ad
+                                for event in events:
+                                    ad.log.info("Got event %s", event["name"])
+                                    if event["name"] == EventSmsSentFailure or \
+                                        event["name"] == EventSmsDeliverFailure:
+                                        if event.get("data") and \
+                                            event["data"].get("Reason"):
+                                            ad.log.error(
+                                                "%s with reason: %s",
+                                                event["name"],
+                                                event["data"]["Reason"])
+                                        res = False
+                                    elif event["name"] == EventSmsSentSuccess \
+                                        or event["name"] == EventSmsDeliverSuccess:
+                                        break
+                            if not res:
+                                return False
+
+
+                except Empty:
+                    _ad.log.error("No %s or %s event for SMS of length %s.",
+                                    EventSmsSentSuccess, EventSmsSentFailure,
+                                    length)
+                    return False
+
+            except Exception as e:
+                log.error("Exception error %s", e)
+                raise
+
+    ad_rx.wait_for_boot_completion()
+    ad_rx.root_adb()
+    ad_rx.start_services(skip_setup_wizard=False)
+
+    output = ad_rx.adb.logcat("-t 1")
+    match = re.search(r"\d+-\d+\s\d+:\d+:\d+.\d+", output)
+    if match:
+        ad_rx.test_log_begin_time = match.group(0)
+
+    ad_rx.messaging_droid, ad_rx.messaging_ed = ad_rx.get_droid()
+    ad_rx.messaging_ed.start()
+    ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
+    time.sleep(1)  #sleep 100ms after starting event tracking
+
+    if not toggle_airplane_mode(log, ad_rx, False):
+        ad_rx.log.error("Failed to disable Airplane Mode")
+        return False
+
+    res = True
+    try:
+        if not wait_for_matching_multiple_sms(log,
+                ad_rx,
+                phonenumber_tx,
+                phonenumber_tx2,
+                messages=message_dict,
+                max_wait_time=max_wait_time):
+            res =  False
+    except Exception as e:
+        log.error("Exception error %s", e)
+        raise
+    finally:
+        ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
+
+    return res
+
+def is_sms_match(event, phonenumber_tx, text):
+    """Return True if 'text' equals to event['data']['Text']
+        and phone number match.
+
+    Args:
+        event: Event object to verify.
+        phonenumber_tx: phone number for sender.
+        text: text string to verify.
+
+    Returns:
+        Return True if 'text' equals to event['data']['Text']
+            and phone number match.
+    """
+    return (check_phone_number_match(event['data']['Sender'], phonenumber_tx)
+            and event['data']['Text'].strip() == text)
+
+def is_sms_partial_match(event, phonenumber_tx, text):
+    """Return True if 'text' starts with event['data']['Text']
+        and phone number match.
+
+    Args:
+        event: Event object to verify.
+        phonenumber_tx: phone number for sender.
+        text: text string to verify.
+
+    Returns:
+        Return True if 'text' starts with event['data']['Text']
+            and phone number match.
+    """
+    event_text = event['data']['Text'].strip()
+    if event_text.startswith("("):
+        event_text = event_text.split(")")[-1]
+    return (check_phone_number_match(event['data']['Sender'], phonenumber_tx)
+            and text.startswith(event_text))
+
+def is_sms_in_collision_match(
+    event, phonenumber_tx, phonenumber_tx2, text, text2):
+    event_text = event['data']['Text'].strip()
+    if event_text.startswith("("):
+        event_text = event_text.split(")")[-1]
+
+    for phonenumber, txt in [[phonenumber_tx, text], [phonenumber_tx2, text2]]:
+        if check_phone_number_match(
+            event['data']['Sender'], phonenumber) and txt.startswith(event_text):
+            return True
+    return False
+
+def is_sms_in_collision_partial_match(
+    event, phonenumber_tx, phonenumber_tx2, text, text2):
+    for phonenumber, txt in [[phonenumber_tx, text], [phonenumber_tx2, text2]]:
+        if check_phone_number_match(
+            event['data']['Sender'], phonenumber) and \
+                event['data']['Text'].strip() == txt:
+            return True
+    return False
+
+def is_sms_match_among_multiple_sms(
+    event, phonenumber_tx, phonenumber_tx2, texts=[], texts2=[]):
+    for txt in texts:
+        if check_phone_number_match(
+            event['data']['Sender'], phonenumber_tx) and \
+                event['data']['Text'].strip() == txt:
+                return True
+
+    for txt in texts2:
+        if check_phone_number_match(
+            event['data']['Sender'], phonenumber_tx2) and \
+                event['data']['Text'].strip() == txt:
+                return True
+
+    return False
+
+def is_sms_partial_match_among_multiple_sms(
+    event, phonenumber_tx, phonenumber_tx2, texts=[], texts2=[]):
+    event_text = event['data']['Text'].strip()
+    if event_text.startswith("("):
+        event_text = event_text.split(")")[-1]
+
+    for txt in texts:
+        if check_phone_number_match(
+            event['data']['Sender'], phonenumber_tx) and \
+                txt.startswith(event_text):
+                return True
+
+    for txt in texts2:
+        if check_phone_number_match(
+            event['data']['Sender'], phonenumber_tx2) and \
+                txt.startswith(event_text):
+                return True
+
+    return False
+
+def wait_for_matching_sms(log,
+                          ad_rx,
+                          phonenumber_tx,
+                          text,
+                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
+                          allow_multi_part_long_sms=True):
+    """Wait for matching incoming SMS.
+
+    Args:
+        log: Log object.
+        ad_rx: Receiver's Android Device Object
+        phonenumber_tx: Sender's phone number.
+        text: SMS content string.
+        allow_multi_part_long_sms: is long SMS allowed to be received as
+            multiple short SMS. This is optional, default value is True.
+
+    Returns:
+        True if matching incoming SMS is received.
+    """
+    if not allow_multi_part_long_sms:
+        try:
+            ad_rx.messaging_ed.wait_for_event(EventSmsReceived, is_sms_match,
+                                              max_wait_time, phonenumber_tx,
+                                              text)
+            ad_rx.log.info("Got event %s", EventSmsReceived)
+            return True
+        except Empty:
+            ad_rx.log.error("No matched SMS received event.")
+            return False
+    else:
+        try:
+            received_sms = ''
+            remaining_text = text
+            while (remaining_text != ''):
+                event = ad_rx.messaging_ed.wait_for_event(
+                    EventSmsReceived, is_sms_partial_match, max_wait_time,
+                    phonenumber_tx, remaining_text)
+                event_text = event['data']['Text'].split(")")[-1].strip()
+                event_text_length = len(event_text)
+                ad_rx.log.info("Got event %s of text length %s from %s",
+                               EventSmsReceived, event_text_length,
+                               phonenumber_tx)
+                remaining_text = remaining_text[event_text_length:]
+                received_sms += event_text
+            ad_rx.log.info("Received SMS of length %s", len(received_sms))
+            return True
+        except Empty:
+            ad_rx.log.error(
+                "Missing SMS received event of text length %s from %s",
+                len(remaining_text), phonenumber_tx)
+            if received_sms != '':
+                ad_rx.log.error(
+                    "Only received partial matched SMS of length %s",
+                    len(received_sms))
+            return False
+
+def wait_for_matching_mt_sms_in_collision(log,
+                          ad_rx,
+                          phonenumber_tx,
+                          phonenumber_tx2,
+                          text,
+                          text2,
+                          allow_multi_part_long_sms=True,
+                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+
+    if not allow_multi_part_long_sms:
+        try:
+            ad_rx.messaging_ed.wait_for_event(
+                EventSmsReceived,
+                is_sms_in_collision_match,
+                max_wait_time,
+                phonenumber_tx,
+                phonenumber_tx2,
+                text,
+                text2)
+            ad_rx.log.info("Got event %s", EventSmsReceived)
+            return True
+        except Empty:
+            ad_rx.log.error("No matched SMS received event.")
+            return False
+    else:
+        try:
+            received_sms = ''
+            received_sms2 = ''
+            remaining_text = text
+            remaining_text2 = text2
+            while (remaining_text != '' or remaining_text2 != ''):
+                event = ad_rx.messaging_ed.wait_for_event(
+                    EventSmsReceived,
+                    is_sms_in_collision_partial_match,
+                    max_wait_time,
+                    phonenumber_tx,
+                    phonenumber_tx2,
+                    remaining_text,
+                    remaining_text2)
+                event_text = event['data']['Text'].split(")")[-1].strip()
+                event_text_length = len(event_text)
+
+                if event_text in remaining_text:
+                    ad_rx.log.info("Got event %s of text length %s from %s",
+                                   EventSmsReceived, event_text_length,
+                                   phonenumber_tx)
+                    remaining_text = remaining_text[event_text_length:]
+                    received_sms += event_text
+                elif event_text in remaining_text2:
+                    ad_rx.log.info("Got event %s of text length %s from %s",
+                                   EventSmsReceived, event_text_length,
+                                   phonenumber_tx2)
+                    remaining_text2 = remaining_text2[event_text_length:]
+                    received_sms2 += event_text
+
+            ad_rx.log.info("Received SMS of length %s", len(received_sms))
+            ad_rx.log.info("Received SMS of length %s", len(received_sms2))
+            return True
+        except Empty:
+            ad_rx.log.error(
+                "Missing SMS received event.")
+            if received_sms != '':
+                ad_rx.log.error(
+                    "Only received partial matched SMS of length %s from %s",
+                    len(received_sms), phonenumber_tx)
+            if received_sms2 != '':
+                ad_rx.log.error(
+                    "Only received partial matched SMS of length %s from %s",
+                    len(received_sms2), phonenumber_tx2)
+            return False
+
+def wait_for_matching_mt_sms_in_collision_with_mo_sms(log,
+                          ad_rx,
+                          ad_rx2,
+                          phonenumber_tx,
+                          phonenumber_tx2,
+                          text,
+                          text2,
+                          allow_multi_part_long_sms=True,
+                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
+
+    if not allow_multi_part_long_sms:
+        result = True
+        try:
+            ad_rx.messaging_ed.wait_for_call_offhook_event(
+                EventSmsReceived,
+                is_sms_match,
+                max_wait_time,
+                phonenumber_tx,
+                text)
+            ad_rx.log.info("Got event %s", EventSmsReceived)
+        except Empty:
+            ad_rx.log.error("No matched SMS received event.")
+            result = False
+
+        try:
+            ad_rx2.messaging_ed.wait_for_call_offhook_event(
+                EventSmsReceived,
+                is_sms_match,
+                max_wait_time,
+                phonenumber_tx2,
+                text2)
+            ad_rx2.log.info("Got event %s", EventSmsReceived)
+        except Empty:
+            ad_rx2.log.error("No matched SMS received event.")
+            result = False
+
+        return result
+    else:
+        result = True
+        try:
+            received_sms = ''
+            remaining_text = text
+            while remaining_text != '':
+                event = ad_rx.messaging_ed.wait_for_event(
+                    EventSmsReceived, is_sms_partial_match, max_wait_time,
+                    phonenumber_tx, remaining_text)
+                event_text = event['data']['Text'].split(")")[-1].strip()
+                event_text_length = len(event_text)
+
+                if event_text in remaining_text:
+                    ad_rx.log.info("Got event %s of text length %s from %s",
+                                   EventSmsReceived, event_text_length,
+                                   phonenumber_tx)
+                    remaining_text = remaining_text[event_text_length:]
+                    received_sms += event_text
+
+            ad_rx.log.info("Received SMS of length %s", len(received_sms))
+        except Empty:
+            ad_rx.log.error(
+                "Missing SMS received event.")
+            if received_sms != '':
+                ad_rx.log.error(
+                    "Only received partial matched SMS of length %s from %s",
+                    len(received_sms), phonenumber_tx)
+            result = False
+
+        try:
+            received_sms2 = ''
+            remaining_text2 = text2
+            while remaining_text2 != '':
+                event2 = ad_rx2.messaging_ed.wait_for_event(
+                    EventSmsReceived, is_sms_partial_match, max_wait_time,
+                    phonenumber_tx2, remaining_text2)
+                event_text2 = event2['data']['Text'].split(")")[-1].strip()
+                event_text_length2 = len(event_text2)
+
+                if event_text2 in remaining_text2:
+                    ad_rx2.log.info("Got event %s of text length %s from %s",
+                                   EventSmsReceived, event_text_length2,
+                                   phonenumber_tx2)
+                    remaining_text2 = remaining_text2[event_text_length2:]
+                    received_sms2 += event_text2
+
+            ad_rx2.log.info("Received SMS of length %s", len(received_sms2))
+        except Empty:
+            ad_rx2.log.error(
+                "Missing SMS received event.")
+            if received_sms2 != '':
+                ad_rx2.log.error(
+                    "Only received partial matched SMS of length %s from %s",
+                    len(received_sms2), phonenumber_tx2)
+            result = False
+
+        return result
+
+def wait_for_matching_multiple_sms(log,
+                        ad_rx,
+                        phonenumber_tx,
+                        phonenumber_tx2,
+                        messages={},
+                        allow_multi_part_long_sms=True,
+                        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
+
+    if not allow_multi_part_long_sms:
+        try:
+            ad_rx.messaging_ed.wait_for_event(
+                EventSmsReceived,
+                is_sms_match_among_multiple_sms,
+                max_wait_time,
+                phonenumber_tx,
+                phonenumber_tx2,
+                messages[phonenumber_tx],
+                messages[phonenumber_tx2])
+            ad_rx.log.info("Got event %s", EventSmsReceived)
+            return True
+        except Empty:
+            ad_rx.log.error("No matched SMS received event.")
+            return False
+    else:
+        all_msgs = []
+        for tx, msgs in messages.items():
+            for msg in msgs:
+                all_msgs.append([tx, msg, msg, ''])
+
+        all_msgs_copy = all_msgs.copy()
+
+        try:
+            while (all_msgs != []):
+                event = ad_rx.messaging_ed.wait_for_event(
+                    EventSmsReceived,
+                    is_sms_partial_match_among_multiple_sms,
+                    max_wait_time,
+                    phonenumber_tx,
+                    phonenumber_tx2,
+                    messages[phonenumber_tx],
+                    messages[phonenumber_tx2])
+                event_text = event['data']['Text'].split(")")[-1].strip()
+                event_text_length = len(event_text)
+
+                for msg in all_msgs_copy:
+                    if event_text in msg[2]:
+                        ad_rx.log.info("Got event %s of text length %s from %s",
+                                       EventSmsReceived, event_text_length,
+                                       msg[0])
+                        msg[2] = msg[2][event_text_length:]
+                        msg[3] += event_text
+
+                        if msg[2] == "":
+                            all_msgs.remove(msg)
+
+            ad_rx.log.info("Received all SMS' sent when power-off.")
+        except Empty:
+            ad_rx.log.error(
+                "Missing SMS received event.")
+
+            for msg in all_msgs_copy:
+                if msg[3] != '':
+                    ad_rx.log.error(
+                        "Only received partial matched SMS of length %s from %s",
+                        len(msg[3]), msg[0])
+            return False
+
+        return True
+
+def wait_for_sending_sms(ad_tx, max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
+    try:
+        events = ad_tx.messaging_ed.pop_events(
+            "(%s|%s|%s|%s)" %
+            (EventSmsSentSuccess, EventSmsSentFailure,
+                EventSmsDeliverSuccess,
+                EventSmsDeliverFailure), max_wait_time)
+        for event in events:
+            ad_tx.log.info("Got event %s", event["name"])
+            if event["name"] == EventSmsSentFailure or \
+                event["name"] == EventSmsDeliverFailure:
+                if event.get("data") and event["data"].get("Reason"):
+                    ad_tx.log.error("%s with reason: %s",
+                                    event["name"],
+                                    event["data"]["Reason"])
+                return False
+            elif event["name"] == EventSmsSentSuccess or \
+                event["name"] == EventSmsDeliverSuccess:
+                return True
+    except Empty:
+        ad_tx.log.error("No %s or %s event for SMS.",
+                        EventSmsSentSuccess, EventSmsSentFailure)
+        return False
+
+def voice_call_in_collision_with_mt_sms_msim(
+        log,
+        ad_primary,
+        ad_sms,
+        ad_voice,
+        sms_subid_ad_primary,
+        sms_subid_ad_sms,
+        voice_subid_ad_primary,
+        voice_subid_ad_voice,
+        array_message,
+        ad_hangup=None,
+        verify_caller_func=None,
+        verify_callee_func=None,
+        call_direction="mo",
+        wait_time_in_call=WAIT_TIME_IN_CALL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+        dialing_number_length=None,
+        video_state=None):
+
+    ad_tx = ad_sms
+    ad_rx = ad_primary
+    subid_tx = sms_subid_ad_sms
+    subid_rx = sms_subid_ad_primary
+
+    if call_direction == "mo":
+        ad_caller = ad_primary
+        ad_callee = ad_voice
+        subid_caller = voice_subid_ad_primary
+        subid_callee = voice_subid_ad_voice
+    elif call_direction == "mt":
+        ad_callee = ad_primary
+        ad_caller = ad_voice
+        subid_callee = voice_subid_ad_primary
+        subid_caller = voice_subid_ad_voice
+
+
+    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
+    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
+
+    tel_result_wrapper = TelResultWrapper(CallResult('SUCCESS'))
+
+    for ad in (ad_tx, ad_rx):
+        ad.send_keycode("BACK")
+        if not getattr(ad, "messaging_droid", None):
+            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+            ad.messaging_ed.start()
+        else:
+            try:
+                if not ad.messaging_droid.is_live:
+                    ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                    ad.messaging_ed.start()
+                else:
+                    ad.messaging_ed.clear_all_events()
+            except Exception:
+                ad.log.info("Create new sl4a session for messaging")
+                ad.messaging_droid, ad.messaging_ed = ad.get_droid()
+                ad.messaging_ed.start()
+
+    if not verify_caller_func:
+        verify_caller_func = is_phone_in_call
+    if not verify_callee_func:
+        verify_callee_func = is_phone_in_call
+
+    caller_number = ad_caller.telephony['subscription'][subid_caller][
+        'phone_num']
+    callee_number = ad_callee.telephony['subscription'][subid_callee][
+        'phone_num']
+    if dialing_number_length:
+        skip_test = False
+        trunc_position = 0 - int(dialing_number_length)
+        try:
+            caller_area_code = caller_number[:trunc_position]
+            callee_area_code = callee_number[:trunc_position]
+            callee_dial_number = callee_number[trunc_position:]
+        except:
+            skip_test = True
+        if caller_area_code != callee_area_code:
+            skip_test = True
+        if skip_test:
+            msg = "Cannot make call from %s to %s by %s digits" % (
+                caller_number, callee_number, dialing_number_length)
+            ad_caller.log.info(msg)
+            raise signals.TestSkip(msg)
+        else:
+            callee_number = callee_dial_number
+
+    msg = "Call from %s to %s" % (caller_number, callee_number)
+    if video_state:
+        msg = "Video %s" % msg
+        video = True
+    else:
+        video = False
+    if ad_hangup:
+        msg = "%s for duration of %s seconds" % (msg, wait_time_in_call)
+    ad_caller.log.info(msg)
+
+    for ad in (ad_caller, ad_callee):
+        call_ids = ad.droid.telecomCallGetCallIds()
+        setattr(ad, "call_ids", call_ids)
+        if call_ids:
+            ad.log.info("Pre-exist CallId %s before making call", call_ids)
+
+    ad_caller.ed.clear_events(EventCallStateChanged)
+    call_begin_time = get_device_epoch_time(ad)
+    ad_caller.droid.telephonyStartTrackingCallStateForSubscription(subid_caller)
+
+    for text in array_message:
+        length = len(text)
+        ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
+                       phonenumber_tx, phonenumber_rx, length, text)
+        try:
+            ad_rx.messaging_ed.clear_events(EventSmsReceived)
+            ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
+            ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
+            ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
+            time.sleep(1)  #sleep 100ms after starting event tracking
+            ad_tx.messaging_droid.logI("Sending SMS of length %s" % length)
+            ad_rx.messaging_droid.logI("Expecting SMS of length %s" % length)
+            ad_caller.log.info("Make a phone call to %s", callee_number)
+
+            tasks = [
+                (ad_tx.messaging_droid.smsSendTextMessage,
+                (phonenumber_rx, text, True)),
+                (ad_caller.droid.telecomCallNumber,
+                (callee_number, video))]
+
+            run_multithread_func(log, tasks)
+
+            try:
+                # Verify OFFHOOK state
+                if not wait_for_call_offhook_for_subscription(
+                        log,
+                        ad_caller,
+                        subid_caller,
+                        event_tracking_started=True):
+                    ad_caller.log.info(
+                        "sub_id %s not in call offhook state", subid_caller)
+                    last_call_drop_reason(ad_caller, begin_time=call_begin_time)
+
+                    ad_caller.log.error("Initiate call failed.")
+                    tel_result_wrapper.result_value = CallResult(
+                                                        'INITIATE_FAILED')
+                    return tel_result_wrapper
+                else:
+                    ad_caller.log.info("Caller initate call successfully")
+            finally:
+                ad_caller.droid.telephonyStopTrackingCallStateChangeForSubscription(
+                    subid_caller)
+                if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
+                    ad_caller.droid.telecomShowInCallScreen()
+                elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
+                    ad_caller.droid.showHomeScreen()
+
+            if not wait_and_answer_call_for_subscription(
+                    log,
+                    ad_callee,
+                    subid_callee,
+                    incoming_number=caller_number,
+                    caller=ad_caller,
+                    incall_ui_display=incall_ui_display,
+                    video_state=video_state):
+                ad_callee.log.error("Answer call fail.")
+                tel_result_wrapper.result_value = CallResult(
+                    'NO_RING_EVENT_OR_ANSWER_FAILED')
+                return tel_result_wrapper
+            else:
+                ad_callee.log.info("Callee answered the call successfully")
+
+            for ad, call_func in zip([ad_caller, ad_callee],
+                                     [verify_caller_func, verify_callee_func]):
+                call_ids = ad.droid.telecomCallGetCallIds()
+                new_call_ids = set(call_ids) - set(ad.call_ids)
+                if not new_call_ids:
+                    ad.log.error(
+                        "No new call ids are found after call establishment")
+                    ad.log.error("telecomCallGetCallIds returns %s",
+                                 ad.droid.telecomCallGetCallIds())
+                    tel_result_wrapper.result_value = CallResult(
+                                                        'NO_CALL_ID_FOUND')
+                for new_call_id in new_call_ids:
+                    if not wait_for_in_call_active(ad, call_id=new_call_id):
+                        tel_result_wrapper.result_value = CallResult(
+                            'CALL_STATE_NOT_ACTIVE_DURING_ESTABLISHMENT')
+                    else:
+                        ad.log.info(
+                            "callProperties = %s",
+                            ad.droid.telecomCallGetProperties(new_call_id))
+
+                if not ad.droid.telecomCallGetAudioState():
+                    ad.log.error("Audio is not in call state")
+                    tel_result_wrapper.result_value = CallResult(
+                        'AUDIO_STATE_NOT_INCALL_DURING_ESTABLISHMENT')
+
+                if call_func(log, ad):
+                    ad.log.info("Call is in %s state", call_func.__name__)
+                else:
+                    ad.log.error("Call is not in %s state, voice in RAT %s",
+                                 call_func.__name__,
+                                 ad.droid.telephonyGetCurrentVoiceNetworkType())
+                    tel_result_wrapper.result_value = CallResult(
+                        'CALL_DROP_OR_WRONG_STATE_DURING_ESTABLISHMENT')
+            if not tel_result_wrapper:
+                return tel_result_wrapper
+
+            if not wait_for_sending_sms(
+                ad_tx,
+                max_wait_time=MAX_WAIT_TIME_SMS_SENT_SUCCESS_IN_COLLISION):
+                return False
+
+            tasks = [
+                (wait_for_matching_sms,
+                (log, ad_rx, phonenumber_tx, text,
+                MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION, True)),
+                (wait_for_call_end,
+                (log, ad_caller, ad_callee, ad_hangup, verify_caller_func,
+                    verify_callee_func, call_begin_time, 5, tel_result_wrapper,
+                    WAIT_TIME_IN_CALL))]
+
+            results = run_multithread_func(log, tasks)
+
+            if not results[0]:
+                ad_rx.log.error("No matching received SMS of length %s.",
+                                length)
+                return False
+
+            tel_result_wrapper = results[1]
+
+        except Exception as e:
+            log.error("Exception error %s", e)
+            raise
+        finally:
+            ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
+
+    return tel_result_wrapper
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_parse_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_parse_utils.py
index dedfc24..e4366cd 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_parse_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_parse_utils.py
@@ -14,12 +14,15 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import time
-import random
+import copy
 import re
 import statistics
 
+from acts import signals
+from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_data_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_voice_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
 
 SETUP_DATA_CALL = 'SETUP_DATA_CALL'
 SETUP_DATA_CALL_REQUEST = '> SETUP_DATA_CALL'
@@ -47,6 +50,19 @@
 WHI_IWLAN_DEACTIVATE_DATA_CALL_REQUEST = r'IwlanDataService\[\d\]: Deactivate data call'
 WHI_IWLAN_DEACTIVATE_DATA_CALL_RESPONSE = r'IwlanDataService\[\d\]: Tunnel closed!'
 
+ON_ENABLE_APN_IMS_SLOT0 = 'DCT-C-0 : onEnableApn: apnType=ims, request type=NORMAL'
+ON_ENABLE_APN_IMS_SLOT1 = 'DCT-C-1 : onEnableApn: apnType=ims, request type=NORMAL'
+ON_ENABLE_APN_IMS_HANDOVER_SLOT0 = 'DCT-C-0 : onEnableApn: apnType=ims, request type=HANDOVER'
+ON_ENABLE_APN_IMS_HANDOVER_SLOT1 = 'DCT-C-1 : onEnableApn: apnType=ims, request type=HANDOVER'
+RADIO_ON_4G_SLOT0 = r'GsmCdmaPhone: \[0\] Event EVENT_RADIO_ON Received'
+RADIO_ON_4G_SLOT1 = r'GsmCdmaPhone: \[1\] Event EVENT_RADIO_ON Received'
+RADIO_ON_IWLAN = 'Switching to new default network.*WIFI CONNECTED'
+WIFI_OFF = 'setWifiEnabled.*enable=false'
+ON_IMS_MM_TEL_CONNECTED_4G_SLOT0 = r'ImsPhone: \[0\].*onImsMmTelConnected imsRadioTech=WWAN'
+ON_IMS_MM_TEL_CONNECTED_4G_SLOT1 = r'ImsPhone: \[1\].*onImsMmTelConnected imsRadioTech=WWAN'
+ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0 = r'ImsPhone: \[0\].*onImsMmTelConnected imsRadioTech=WLAN'
+ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1 = r'ImsPhone: \[1\].*onImsMmTelConnected imsRadioTech=WLAN'
+
 def print_nested_dict(ad, d):
     divider = "------"
     for k, v in d.items():
@@ -1057,4 +1073,170 @@
     return (
         deactivate_data_call,
         deactivate_data_call_time_list,
-        avg_deactivate_data_call_time)
\ No newline at end of file
+        avg_deactivate_data_call_time)
+
+def parse_ims_reg(
+    ad,
+    search_intervals=None,
+    rat='4g',
+    reboot_or_apm='reboot',
+    slot=None):
+    """Search in logcat for lines containing messages about IMS registration.
+
+    Args:
+        ad: Android object
+        search_intervals: List. Only lines with time stamp in given time
+            intervals will be parsed.
+            E.g., [(begin_time1, end_time1), (begin_time2, end_time2)]
+            Both begin_time and end_time should be datetime object.
+        rat: "4g" for IMS over LTE or "iwlan" for IMS over Wi-Fi
+        reboot_or_apm: specify the scenario "reboot" or "apm"
+        slot: 0 for pSIM and 1 for eSIM
+
+    Returns:
+        (ims_reg, parsing_fail, avg_ims_reg_duration)
+
+        ims_reg: List of dictionaries containing found lines for start and
+            end time stamps. Each dict represents a cycle of the test.
+
+            [
+                {'start': message on start time stamp,
+                'end': message on end time stamp,
+                'duration': time difference between start and end}
+            ]
+        parsing_fail: List of dictionaries containing the cycle number and
+            missing messages of each failed cycle
+
+            [
+                'attempt': failed cycle number
+                'missing_msg' missing messages which should be found
+            ]
+        avg_ims_reg_duration: average of the duration in ims_reg
+
+    """
+    if slot is None:
+        slot = get_slot_index_from_voice_sub_id(ad)
+        ad.log.info('Default voice slot: %s', slot)
+    else:
+        if get_subid_from_slot_index(ad.log, ad, slot) == INVALID_SUB_ID:
+            ad.log.error('Slot %s is invalid.', slot)
+            raise signals.TestFailure('Failed',
+                extras={'fail_reason': 'Slot %s is invalid.' % slot})
+
+        ad.log.info('Assigned slot: %s', slot)
+
+    start_command = {
+        'reboot': {
+            '0': {'4g': ON_ENABLE_APN_IMS_SLOT0,
+                'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT0 + '\|' + ON_ENABLE_APN_IMS_SLOT0},
+            '1': {'4g': ON_ENABLE_APN_IMS_SLOT1,
+                'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT1 + '\|' + ON_ENABLE_APN_IMS_SLOT1}
+        },
+        'apm':{
+            '0': {'4g': RADIO_ON_4G_SLOT0, 'iwlan': RADIO_ON_IWLAN},
+            '1': {'4g': RADIO_ON_4G_SLOT1, 'iwlan': RADIO_ON_IWLAN}
+        },
+        'wifi_off':{
+            '0': {'4g': WIFI_OFF, 'iwlan': WIFI_OFF},
+            '1': {'4g': WIFI_OFF, 'iwlan': WIFI_OFF}
+        },
+    }
+
+    end_command = {
+        '0': {'4g': ON_IMS_MM_TEL_CONNECTED_4G_SLOT0,
+            'iwlan': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0},
+        '1': {'4g': ON_IMS_MM_TEL_CONNECTED_4G_SLOT1,
+            'iwlan': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1}
+    }
+
+    ad.log.info('====== Start to search logcat ======')
+    logcat = ad.search_logcat('%s\|%s' % (
+        start_command[reboot_or_apm][str(slot)][rat],
+        end_command[str(slot)][rat]))
+
+    if not logcat:
+        raise signals.TestFailure('Failed',
+            extras={'fail_reason': 'No line matching the given pattern can '
+            'be found in logcat.'})
+
+    for msg in logcat:
+        ad.log.info(msg["log_message"])
+
+    ims_reg = []
+    ims_reg_duration_list = []
+    parsing_fail = []
+
+    start_command['reboot'] = {
+        '0': {'4g': ON_ENABLE_APN_IMS_SLOT0,
+            'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT0 + '|' + ON_ENABLE_APN_IMS_SLOT0},
+        '1': {'4g': ON_ENABLE_APN_IMS_SLOT1,
+            'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT1 + '|' + ON_ENABLE_APN_IMS_SLOT1}
+    }
+
+    keyword_dict = {
+        'start': start_command[reboot_or_apm][str(slot)][rat],
+        'end': end_command[str(slot)][rat]
+    }
+
+    for attempt, interval in enumerate(search_intervals):
+        if isinstance(interval, list):
+            try:
+                begin_time, end_time = interval
+            except Exception as e:
+                ad.log.error(e)
+                continue
+
+            ad.log.info('Parsing begin time: %s', begin_time)
+            ad.log.info('Parsing end time: %s', end_time)
+
+            temp_keyword_dict = copy.deepcopy(keyword_dict)
+            for line in logcat:
+                if begin_time and line['datetime_obj'] < begin_time:
+                    continue
+
+                if end_time and line['datetime_obj'] > end_time:
+                    break
+
+                for key in temp_keyword_dict:
+                    if temp_keyword_dict[key] and not isinstance(
+                        temp_keyword_dict[key], dict):
+                        res = re.findall(
+                            temp_keyword_dict[key], line['log_message'])
+                        if res:
+                            ad.log.info('Found: %s', line['log_message'])
+                            temp_keyword_dict[key] = {
+                                'message': line['log_message'],
+                                'time_stamp': line['datetime_obj']}
+                            break
+
+            for key in temp_keyword_dict:
+                if temp_keyword_dict[key] == keyword_dict[key]:
+                    ad.log.error(
+                        '"%s" is missing in cycle %s.',
+                        keyword_dict[key],
+                        attempt)
+                    parsing_fail.append({
+                        'attempt': attempt,
+                        'missing_msg': keyword_dict[key]})
+            try:
+                ims_reg_duration = (
+                    temp_keyword_dict['end'][
+                        'time_stamp'] - temp_keyword_dict[
+                            'start'][
+                                'time_stamp']).total_seconds()
+                ims_reg_duration_list.append(ims_reg_duration)
+                ims_reg.append({
+                    'start': temp_keyword_dict['start'][
+                        'message'],
+                    'end': temp_keyword_dict['end'][
+                        'message'],
+                    'duration': ims_reg_duration})
+            except Exception as e:
+                ad.log.error(e)
+
+    try:
+        avg_ims_reg_duration = statistics.mean(ims_reg_duration_list)
+    except:
+        avg_ims_reg_duration = None
+
+    return ims_reg, parsing_fail, avg_ims_reg_duration
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_sms_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_sms_utils.py
index dc68671..408fee1 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_sms_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_sms_utils.py
@@ -17,7 +17,7 @@
 
 import time
 from acts.utils import rand_ascii_str
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_message_sub_id
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_subscription_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_subscription_utils.py
index c9134e9..bab91a6 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_subscription_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_subscription_utils.py
@@ -21,14 +21,13 @@
 
 from acts_contrib.test_utils.tel.tel_defines import CHIPSET_MODELS_LIST
 from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_DATA_SUB_ID
 from future import standard_library
 
 standard_library.install_aliases()
 
 
-def initial_set_up_for_subid_infomation(log, ad):
+def initial_set_up_for_subid_information(log, ad):
     """Initial subid setup for voice, message and data according to ad's
     attribute.
 
@@ -593,3 +592,38 @@
         if info['subscriptionId'] == data_sub_id:
             return info['simSlotIndex']
     return INVALID_SUB_ID
+
+def get_slot_index_from_voice_sub_id(ad):
+    """Get slot index from the current voice sub ID.
+
+    Args:
+        ad: android object
+
+    Returns:
+        0: pSIM
+        1: eSIM
+        INVALID_SUB_ID (-1): if no sub ID is equal to current voice sub ID.
+    """
+    voice_sub_id = get_incoming_voice_sub_id(ad)
+    sub_info = ad.droid.subscriptionGetAllSubInfoList()
+    for info in sub_info:
+        if info['subscriptionId'] == voice_sub_id:
+            return info['simSlotIndex']
+    return INVALID_SUB_ID
+
+def get_all_sub_id(ad):
+    """Return all valid subscription IDs.
+
+    Args:
+        ad: Android object
+
+    Returns:
+        List containing all valid subscription IDs.
+    """
+    sub_id_list = []
+    sub_info = ad.droid.subscriptionGetAllSubInfoList()
+    for info in sub_info:
+        if info['simSlotIndex'] != INVALID_SUB_ID:
+            sub_id_list.append(info['subscriptionId'])
+
+    return sub_id_list
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/tel_test_utils.py b/acts_tests/acts_contrib/test_utils/tel/tel_test_utils.py
index d3452ef..44ef53f 100644
--- a/acts_tests/acts_contrib/test_utils/tel/tel_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_test_utils.py
@@ -33,12 +33,11 @@
 from acts import utils
 from queue import Empty
 from acts.asserts import abort_all
-from acts.asserts import fail
 from acts.controllers.adb_lib.error import AdbError
 from acts.controllers.android_device import list_adb_devices
 from acts.controllers.android_device import list_fastboot_devices
 from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
-from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
+
 from acts.controllers.android_device import SL4A_APK_NAME
 from acts.libs.proc import job
 from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
@@ -83,9 +82,6 @@
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_IDLE_EVENT
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_SENT_SUCCESS
-from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_SENT_SUCCESS_IN_COLLISION
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_TELECOM_RINGING
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOICE_MAIL_COUNT
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOLTE_ENABLED
@@ -104,10 +100,7 @@
 from acts_contrib.test_utils.tel.tel_defines import PHONE_NUMBER_STRING_FORMAT_10_DIGIT
 from acts_contrib.test_utils.tel.tel_defines import PHONE_NUMBER_STRING_FORMAT_11_DIGIT
 from acts_contrib.test_utils.tel.tel_defines import PHONE_NUMBER_STRING_FORMAT_12_DIGIT
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_LTE
 from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
-from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
 from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
 from acts_contrib.test_utils.tel.tel_defines import RAT_5G
@@ -125,12 +118,10 @@
 from acts_contrib.test_utils.tel.tel_defines import TELEPHONY_STATE_IDLE
 from acts_contrib.test_utils.tel.tel_defines import TELEPHONY_STATE_OFFHOOK
 from acts_contrib.test_utils.tel.tel_defines import TELEPHONY_STATE_RINGING
-from acts_contrib.test_utils.tel.tel_defines import VOICEMAIL_DELETE_DIGIT
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_1XRTT_VOICE_ATTACH
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
 from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_DATA_SUB_ID
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_LEAVE_VOICE_MAIL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_REJECT_CALL
@@ -147,17 +138,11 @@
 from acts_contrib.test_utils.tel.tel_defines import EventDisplayInfoChanged
 from acts_contrib.test_utils.tel.tel_defines import EventConnectivityChanged
 from acts_contrib.test_utils.tel.tel_defines import EventDataConnectionStateChanged
-from acts_contrib.test_utils.tel.tel_defines import EventDataSmsReceived
 from acts_contrib.test_utils.tel.tel_defines import EventMessageWaitingIndicatorChanged
 from acts_contrib.test_utils.tel.tel_defines import EventServiceStateChanged
 from acts_contrib.test_utils.tel.tel_defines import EventMmsSentFailure
 from acts_contrib.test_utils.tel.tel_defines import EventMmsSentSuccess
 from acts_contrib.test_utils.tel.tel_defines import EventMmsDownloaded
-from acts_contrib.test_utils.tel.tel_defines import EventSmsReceived
-from acts_contrib.test_utils.tel.tel_defines import EventSmsDeliverFailure
-from acts_contrib.test_utils.tel.tel_defines import EventSmsDeliverSuccess
-from acts_contrib.test_utils.tel.tel_defines import EventSmsSentFailure
-from acts_contrib.test_utils.tel.tel_defines import EventSmsSentSuccess
 from acts_contrib.test_utils.tel.tel_defines import CallStateContainer
 from acts_contrib.test_utils.tel.tel_defines import DataConnectionStateContainer
 from acts_contrib.test_utils.tel.tel_defines import MessageWaitingIndicatorContainer
@@ -165,7 +150,6 @@
 from acts_contrib.test_utils.tel.tel_defines import ServiceStateContainer
 from acts_contrib.test_utils.tel.tel_defines import DisplayInfoContainer
 from acts_contrib.test_utils.tel.tel_defines import OverrideNetworkContainer
-from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_LTE_GSM_WCDMA
 from acts_contrib.test_utils.tel.tel_defines import CARRIER_VZW, CARRIER_ATT, \
     CARRIER_BELL, CARRIER_ROGERS, CARRIER_KOODO, CARRIER_VIDEOTRON, CARRIER_TELUS
 from acts_contrib.test_utils.tel.tel_lookup_tables import connection_type_from_type_string
@@ -188,24 +172,17 @@
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_incoming_voice_sub_id
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_incoming_message_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_outgoing_call
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_incoming_voice_sub_id
-from acts_contrib.test_utils.tel.tel_subscription_utils import set_subid_for_message
-from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_on_same_network_of_host_ad
 from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g_for_subscription
 from acts_contrib.test_utils.tel.tel_5g_utils import is_current_network_5g
 from acts_contrib.test_utils.wifi import wifi_test_utils
-from acts_contrib.test_utils.wifi import wifi_constants
 from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
 from acts.utils import adb_shell_ping
 from acts.utils import load_config
 from acts.utils import start_standing_subprocess
-from acts.utils import stop_standing_subprocess
 from acts.logger import epoch_to_log_line_timestamp
-from acts.logger import normalize_log_line_timestamp
 from acts.utils import get_current_epoch_time
 from acts.utils import exe_cmd
-from acts.utils import rand_ascii_str
 
 
 WIFI_SSID_KEY = wifi_test_utils.WifiEnums.SSID_KEY
@@ -6138,134 +6115,6 @@
     return model
 
 
-def is_sms_match(event, phonenumber_tx, text):
-    """Return True if 'text' equals to event['data']['Text']
-        and phone number match.
-
-    Args:
-        event: Event object to verify.
-        phonenumber_tx: phone number for sender.
-        text: text string to verify.
-
-    Returns:
-        Return True if 'text' equals to event['data']['Text']
-            and phone number match.
-    """
-    return (check_phone_number_match(event['data']['Sender'], phonenumber_tx)
-            and event['data']['Text'].strip() == text)
-
-
-def is_sms_partial_match(event, phonenumber_tx, text):
-    """Return True if 'text' starts with event['data']['Text']
-        and phone number match.
-
-    Args:
-        event: Event object to verify.
-        phonenumber_tx: phone number for sender.
-        text: text string to verify.
-
-    Returns:
-        Return True if 'text' starts with event['data']['Text']
-            and phone number match.
-    """
-    event_text = event['data']['Text'].strip()
-    if event_text.startswith("("):
-        event_text = event_text.split(")")[-1]
-    return (check_phone_number_match(event['data']['Sender'], phonenumber_tx)
-            and text.startswith(event_text))
-
-
-def sms_send_receive_verify(log,
-                            ad_tx,
-                            ad_rx,
-                            array_message,
-                            max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
-                            expected_result=True,
-                            slot_id_rx=None):
-    """Send SMS, receive SMS, and verify content and sender's number.
-
-        Send (several) SMS from droid_tx to droid_rx.
-        Verify SMS is sent, delivered and received.
-        Verify received content and sender's number are correct.
-
-    Args:
-        log: Log object.
-        ad_tx: Sender's Android Device Object
-        ad_rx: Receiver's Android Device Object
-        array_message: the array of message to send/receive
-        slot_id_rx: the slot on the Receiver's android device (0/1)
-    """
-    subid_tx = get_outgoing_message_sub_id(ad_tx)
-    if slot_id_rx is None:
-        subid_rx = get_incoming_message_sub_id(ad_rx)
-    else:
-        subid_rx = get_subid_from_slot_index(log, ad_rx, slot_id_rx)
-
-    result = sms_send_receive_verify_for_subscription(
-        log, ad_tx, ad_rx, subid_tx, subid_rx, array_message, max_wait_time)
-    if result != expected_result:
-        log_messaging_screen_shot(ad_tx, test_name="sms_tx")
-        log_messaging_screen_shot(ad_rx, test_name="sms_rx")
-    return result == expected_result
-
-
-def wait_for_matching_sms(log,
-                          ad_rx,
-                          phonenumber_tx,
-                          text,
-                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE,
-                          allow_multi_part_long_sms=True):
-    """Wait for matching incoming SMS.
-
-    Args:
-        log: Log object.
-        ad_rx: Receiver's Android Device Object
-        phonenumber_tx: Sender's phone number.
-        text: SMS content string.
-        allow_multi_part_long_sms: is long SMS allowed to be received as
-            multiple short SMS. This is optional, default value is True.
-
-    Returns:
-        True if matching incoming SMS is received.
-    """
-    if not allow_multi_part_long_sms:
-        try:
-            ad_rx.messaging_ed.wait_for_event(EventSmsReceived, is_sms_match,
-                                              max_wait_time, phonenumber_tx,
-                                              text)
-            ad_rx.log.info("Got event %s", EventSmsReceived)
-            return True
-        except Empty:
-            ad_rx.log.error("No matched SMS received event.")
-            return False
-    else:
-        try:
-            received_sms = ''
-            remaining_text = text
-            while (remaining_text != ''):
-                event = ad_rx.messaging_ed.wait_for_event(
-                    EventSmsReceived, is_sms_partial_match, max_wait_time,
-                    phonenumber_tx, remaining_text)
-                event_text = event['data']['Text'].split(")")[-1].strip()
-                event_text_length = len(event_text)
-                ad_rx.log.info("Got event %s of text length %s from %s",
-                               EventSmsReceived, event_text_length,
-                               phonenumber_tx)
-                remaining_text = remaining_text[event_text_length:]
-                received_sms += event_text
-            ad_rx.log.info("Received SMS of length %s", len(received_sms))
-            return True
-        except Empty:
-            ad_rx.log.error(
-                "Missing SMS received event of text length %s from %s",
-                len(remaining_text), phonenumber_tx)
-            if received_sms != '':
-                ad_rx.log.error(
-                    "Only received partial matched SMS of length %s",
-                    len(received_sms))
-            return False
-
-
 def is_mms_match(event, phonenumber_tx, text):
     """Return True if 'text' equals to event['data']['Text']
         and phone number match.
@@ -6312,103 +6161,6 @@
         return False
 
 
-def sms_send_receive_verify_for_subscription(
-        log,
-        ad_tx,
-        ad_rx,
-        subid_tx,
-        subid_rx,
-        array_message,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-    """Send SMS, receive SMS, and verify content and sender's number.
-
-        Send (several) SMS from droid_tx to droid_rx.
-        Verify SMS is sent, delivered and received.
-        Verify received content and sender's number are correct.
-
-    Args:
-        log: Log object.
-        ad_tx: Sender's Android Device Object..
-        ad_rx: Receiver's Android Device Object.
-        subid_tx: Sender's subsciption ID to be used for SMS
-        subid_rx: Receiver's subsciption ID to be used for SMS
-        array_message: the array of message to send/receive
-    """
-    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
-    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
-
-    for ad in (ad_tx, ad_rx):
-        if not getattr(ad, "messaging_droid", None):
-            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-            ad.messaging_ed.start()
-        else:
-            try:
-                if not ad.messaging_droid.is_live:
-                    ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                    ad.messaging_ed.start()
-                else:
-                    ad.messaging_ed.clear_all_events()
-                ad.messaging_droid.logI(
-                    "Start sms_send_receive_verify_for_subscription test")
-            except Exception:
-                ad.log.info("Create new sl4a session for messaging")
-                ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                ad.messaging_ed.start()
-
-    for text in array_message:
-        length = len(text)
-        ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
-                       phonenumber_tx, phonenumber_rx, length, text)
-        try:
-            ad_rx.messaging_ed.clear_events(EventSmsReceived)
-            ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
-            ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
-            ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
-            time.sleep(1)  #sleep 100ms after starting event tracking
-            ad_tx.messaging_droid.logI("Sending SMS of length %s" % length)
-            ad_rx.messaging_droid.logI("Expecting SMS of length %s" % length)
-            ad_tx.messaging_droid.smsSendTextMessage(phonenumber_rx, text,
-                                                     True)
-            try:
-                events = ad_tx.messaging_ed.pop_events(
-                    "(%s|%s|%s|%s)" %
-                    (EventSmsSentSuccess, EventSmsSentFailure,
-                     EventSmsDeliverSuccess,
-                     EventSmsDeliverFailure), max_wait_time)
-                for event in events:
-                    ad_tx.log.info("Got event %s", event["name"])
-                    if event["name"] == EventSmsSentFailure or event["name"] == EventSmsDeliverFailure:
-                        if event.get("data") and event["data"].get("Reason"):
-                            ad_tx.log.error("%s with reason: %s",
-                                            event["name"],
-                                            event["data"]["Reason"])
-                        return False
-                    elif event["name"] == EventSmsSentSuccess or event["name"] == EventSmsDeliverSuccess:
-                        break
-            except Empty:
-                ad_tx.log.error("No %s or %s event for SMS of length %s.",
-                                EventSmsSentSuccess, EventSmsSentFailure,
-                                length)
-                return False
-
-            if not wait_for_matching_sms(
-                    log,
-                    ad_rx,
-                    phonenumber_tx,
-                    text,
-                    max_wait_time,
-                    allow_multi_part_long_sms=True):
-                ad_rx.log.error("No matching received SMS of length %s.",
-                                length)
-                return False
-        except Exception as e:
-            log.error("Exception error %s", e)
-            raise
-        finally:
-            ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
-    return True
-
-
 def mms_send_receive_verify(log,
                             ad_tx,
                             ad_rx,
@@ -8036,38 +7788,6 @@
         return True
 
 
-
-def start_sdm_logger(ad):
-    """Start SDM logger."""
-    if not getattr(ad, "sdm_log", True): return
-    # Delete existing SDM logs which were created 15 mins prior
-    ad.sdm_log_path = DEFAULT_SDM_LOG_PATH
-    file_count = ad.adb.shell(
-        "find %s -type f -iname sbuff_[0-9]*.sdm* | wc -l" % ad.sdm_log_path)
-    if int(file_count) > 3:
-        seconds = 15 * 60
-        # Remove sdm logs modified more than specified seconds ago
-        ad.adb.shell(
-            "find %s -type f -iname sbuff_[0-9]*.sdm* -not -mtime -%ss -delete" %
-            (ad.sdm_log_path, seconds))
-    # Disable any modem logging already running
-    if not getattr(ad, "enable_always_on_modem_logger", False):
-        ad.adb.shell("setprop persist.vendor.sys.modem.logging.enable false")
-    # start logging
-    cmd = "setprop vendor.sys.modem.logging.enable true"
-    ad.log.debug("start sdm logging")
-    ad.adb.shell(cmd, ignore_status=True)
-    time.sleep(5)
-
-
-def stop_sdm_logger(ad):
-    """Stop SDM logger."""
-    cmd = "setprop vendor.sys.modem.logging.enable false"
-    ad.log.debug("stop sdm logging")
-    ad.adb.shell(cmd, ignore_status=True)
-    time.sleep(5)
-
-
 def stop_qxdm_logger(ad):
     """Stop QXDM logger."""
     for cmd in ("diag_mdlog -k", "killall diag_mdlog"):
@@ -8177,17 +7897,6 @@
     run_multithread_func(log, tasks)
 
 
-def start_sdm_loggers(log, ads):
-    tasks = [(start_sdm_logger, [ad]) for ad in ads
-             if getattr(ad, "sdm_log", True)]
-    if tasks: run_multithread_func(log, tasks)
-
-
-def stop_sdm_loggers(log, ads):
-    tasks = [(stop_sdm_logger, [ad]) for ad in ads]
-    run_multithread_func(log, tasks)
-
-
 def start_nexuslogger(ad):
     """Start Nexus/Pixel Logger Apk."""
     qxdm_logger_apk = None
@@ -10112,817 +9821,10 @@
     return rx_power, tx_power
 
 
-def sms_in_collision_send_receive_verify(
-        log,
-        ad_rx,
-        ad_rx2,
-        ad_tx,
-        ad_tx2,
-        array_message,
-        array_message2,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-    """Send 2 SMS', receive both SMS', and verify content and sender's number of
-       each SMS.
-
-        Send 2 SMS'. One from ad_tx to ad_rx and the other from ad_tx2 to ad_rx2.
-        When ad_rx is identical to ad_rx2, the scenario of SMS' in collision can
-        be tested.
-        Verify both SMS' are sent, delivered and received.
-        Verify received content and sender's number of each SMS is correct.
-
-    Args:
-        log: Log object.
-        ad_tx: Sender's Android Device Object..
-        ad_rx: Receiver's Android Device Object.
-        ad_tx2: 2nd sender's Android Device Object..
-        ad_rx2: 2nd receiver's Android Device Object.
-        array_message: the array of message to send/receive from ad_tx to ad_rx
-        array_message2: the array of message to send/receive from ad_tx2 to
-        ad_rx2
-        max_wait_time: Max time to wait for reception of SMS
-    """
-
-    rx_sub_id = get_outgoing_message_sub_id(ad_rx)
-    rx2_sub_id = get_outgoing_message_sub_id(ad_rx2)
-
-    _, tx_sub_id, _ = get_subid_on_same_network_of_host_ad(
-        [ad_rx, ad_tx, ad_tx2],
-        host_sub_id=rx_sub_id)
-    set_subid_for_message(ad_tx, tx_sub_id)
-
-    _, _, tx2_sub_id = get_subid_on_same_network_of_host_ad(
-        [ad_rx2, ad_tx, ad_tx2],
-        host_sub_id=rx2_sub_id)
-    set_subid_for_message(ad_tx2, tx2_sub_id)
-
-    if not sms_in_collision_send_receive_verify_for_subscription(
-        log,
-        ad_tx,
-        ad_tx2,
-        ad_rx,
-        ad_rx2,
-        tx_sub_id,
-        tx2_sub_id,
-        rx_sub_id,
-        rx_sub_id,
-        array_message,
-        array_message2,
-        max_wait_time):
-        log_messaging_screen_shot(
-            ad_rx, test_name="sms rx subid: %s" % rx_sub_id)
-        log_messaging_screen_shot(
-            ad_rx2, test_name="sms rx2 subid: %s" % rx2_sub_id)
-        log_messaging_screen_shot(
-            ad_tx, test_name="sms tx subid: %s" % tx_sub_id)
-        log_messaging_screen_shot(
-            ad_tx2, test_name="sms tx subid: %s" % tx2_sub_id)
-        return False
-    return True
-
-
-def sms_in_collision_send_receive_verify_for_subscription(
-        log,
-        ad_tx,
-        ad_tx2,
-        ad_rx,
-        ad_rx2,
-        subid_tx,
-        subid_tx2,
-        subid_rx,
-        subid_rx2,
-        array_message,
-        array_message2,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-    """Send 2 SMS', receive both SMS', and verify content and sender's number of
-       each SMS.
-
-        Send 2 SMS'. One from ad_tx to ad_rx and the other from ad_tx2 to ad_rx2.
-        When ad_rx is identical to ad_rx2, the scenario of SMS' in collision can
-        be tested.
-        Verify both SMS' are sent, delivered and received.
-        Verify received content and sender's number of each SMS is correct.
-
-    Args:
-        log: Log object.
-        ad_tx: Sender's Android Device Object..
-        ad_rx: Receiver's Android Device Object.
-        ad_tx2: 2nd sender's Android Device Object..
-        ad_rx2: 2nd receiver's Android Device Object.
-        subid_tx: Sub ID of ad_tx as default Sub ID for outgoing SMS
-        subid_tx2: Sub ID of ad_tx2 as default Sub ID for outgoing SMS
-        subid_rx: Sub ID of ad_rx as default Sub ID for incoming SMS
-        subid_rx2: Sub ID of ad_rx2 as default Sub ID for incoming SMS
-        array_message: the array of message to send/receive from ad_tx to ad_rx
-        array_message2: the array of message to send/receive from ad_tx2 to
-        ad_rx2
-        max_wait_time: Max time to wait for reception of SMS
-    """
-
-    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
-    phonenumber_tx2 = ad_tx2.telephony['subscription'][subid_tx2]['phone_num']
-    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
-    phonenumber_rx2 = ad_rx2.telephony['subscription'][subid_rx2]['phone_num']
-
-    for ad in (ad_tx, ad_tx2, ad_rx, ad_rx2):
-        ad.send_keycode("BACK")
-        if not getattr(ad, "messaging_droid", None):
-            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-            ad.messaging_ed.start()
-        else:
-            try:
-                if not ad.messaging_droid.is_live:
-                    ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                    ad.messaging_ed.start()
-                else:
-                    ad.messaging_ed.clear_all_events()
-                ad.messaging_droid.logI(
-                    "Start sms_send_receive_verify_for_subscription test")
-            except Exception:
-                ad.log.info("Create new sl4a session for messaging")
-                ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                ad.messaging_ed.start()
-
-    for text, text2 in zip(array_message, array_message2):
-        length = len(text)
-        length2 = len(text2)
-        ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
-                       phonenumber_tx, phonenumber_rx, length, text)
-        ad_tx2.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
-                       phonenumber_tx2, phonenumber_rx2, length2, text2)
-
-        try:
-            ad_rx.messaging_ed.clear_events(EventSmsReceived)
-            ad_rx2.messaging_ed.clear_events(EventSmsReceived)
-            ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
-            ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
-            ad_tx2.messaging_ed.clear_events(EventSmsSentSuccess)
-            ad_tx2.messaging_ed.clear_events(EventSmsSentFailure)
-            ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
-            if ad_rx2 != ad_rx:
-                ad_rx2.messaging_droid.smsStartTrackingIncomingSmsMessage()
-            time.sleep(1)
-            ad_tx.messaging_droid.logI("Sending SMS of length %s" % length)
-            ad_tx2.messaging_droid.logI("Sending SMS of length %s" % length2)
-            ad_rx.messaging_droid.logI(
-                "Expecting SMS of length %s from %s" % (length, ad_tx.serial))
-            ad_rx2.messaging_droid.logI(
-                "Expecting SMS of length %s from %s" % (length2, ad_tx2.serial))
-
-            tasks = [
-                (ad_tx.messaging_droid.smsSendTextMessage,
-                (phonenumber_rx, text, True)),
-                (ad_tx2.messaging_droid.smsSendTextMessage,
-                (phonenumber_rx2, text2, True))]
-            multithread_func(log, tasks)
-            try:
-                tasks = [
-                    (ad_tx.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
-                        EventSmsSentSuccess,
-                        EventSmsSentFailure,
-                        EventSmsDeliverSuccess,
-                        EventSmsDeliverFailure), max_wait_time)),
-                    (ad_tx2.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
-                        EventSmsSentSuccess,
-                        EventSmsSentFailure,
-                        EventSmsDeliverSuccess,
-                        EventSmsDeliverFailure), max_wait_time))
-                ]
-                results = run_multithread_func(log, tasks)
-                res = True
-                _ad = ad_tx
-                for ad, events in [(ad_tx, results[0]),(ad_tx2, results[1])]:
-                    _ad = ad
-                    for event in events:
-                        ad.log.info("Got event %s", event["name"])
-                        if event["name"] == EventSmsSentFailure or \
-                            event["name"] == EventSmsDeliverFailure:
-                            if event.get("data") and event["data"].get("Reason"):
-                                ad.log.error("%s with reason: %s",
-                                                event["name"],
-                                                event["data"]["Reason"])
-                            res = False
-                        elif event["name"] == EventSmsSentSuccess or \
-                            event["name"] == EventSmsDeliverSuccess:
-                            break
-                if not res:
-                    return False
-            except Empty:
-                _ad.log.error("No %s or %s event for SMS of length %s.",
-                                EventSmsSentSuccess, EventSmsSentFailure,
-                                length)
-                return False
-            if ad_rx == ad_rx2:
-                if not wait_for_matching_mt_sms_in_collision(
-                    log,
-                    ad_rx,
-                    phonenumber_tx,
-                    phonenumber_tx2,
-                    text,
-                    text2,
-                    max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-
-                    ad_rx.log.error(
-                        "No matching received SMS of length %s from %s.",
-                        length,
-                        ad_rx.serial)
-                    return False
-            else:
-                if not wait_for_matching_mt_sms_in_collision_with_mo_sms(
-                    log,
-                    ad_rx,
-                    ad_rx2,
-                    phonenumber_tx,
-                    phonenumber_tx2,
-                    text,
-                    text2,
-                    max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-                    return False
-        except Exception as e:
-            log.error("Exception error %s", e)
-            raise
-        finally:
-            ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
-            ad_rx2.messaging_droid.smsStopTrackingIncomingSmsMessage()
-    return True
-
-
-def sms_rx_power_off_multiple_send_receive_verify(
-        log,
-        ad_rx,
-        ad_tx,
-        ad_tx2,
-        array_message_length,
-        array_message2_length,
-        num_array_message,
-        num_array_message2,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-
-    rx_sub_id = get_outgoing_message_sub_id(ad_rx)
-
-    _, tx_sub_id, _ = get_subid_on_same_network_of_host_ad(
-        [ad_rx, ad_tx, ad_tx2],
-        host_sub_id=rx_sub_id)
-    set_subid_for_message(ad_tx, tx_sub_id)
-
-    _, _, tx2_sub_id = get_subid_on_same_network_of_host_ad(
-        [ad_rx, ad_tx, ad_tx2],
-        host_sub_id=rx_sub_id)
-    set_subid_for_message(ad_tx2, tx2_sub_id)
-
-    if not sms_rx_power_off_multiple_send_receive_verify_for_subscription(
-        log,
-        ad_tx,
-        ad_tx2,
-        ad_rx,
-        tx_sub_id,
-        tx2_sub_id,
-        rx_sub_id,
-        rx_sub_id,
-        array_message_length,
-        array_message2_length,
-        num_array_message,
-        num_array_message2):
-        log_messaging_screen_shot(
-            ad_rx, test_name="sms rx subid: %s" % rx_sub_id)
-        log_messaging_screen_shot(
-            ad_tx, test_name="sms tx subid: %s" % tx_sub_id)
-        log_messaging_screen_shot(
-            ad_tx2, test_name="sms tx subid: %s" % tx2_sub_id)
-        return False
-    return True
-
-
-def sms_rx_power_off_multiple_send_receive_verify_for_subscription(
-        log,
-        ad_tx,
-        ad_tx2,
-        ad_rx,
-        subid_tx,
-        subid_tx2,
-        subid_rx,
-        subid_rx2,
-        array_message_length,
-        array_message2_length,
-        num_array_message,
-        num_array_message2,
-        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-
-    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
-    phonenumber_tx2 = ad_tx2.telephony['subscription'][subid_tx2]['phone_num']
-    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
-    phonenumber_rx2 = ad_rx.telephony['subscription'][subid_rx2]['phone_num']
-
-    if not toggle_airplane_mode(log, ad_rx, True):
-        ad_rx.log.error("Failed to enable Airplane Mode")
-        return False
-    ad_rx.stop_services()
-    ad_rx.log.info("Rebooting......")
-    ad_rx.adb.reboot()
-
-    message_dict = {phonenumber_tx: [], phonenumber_tx2: []}
-    for index in range(max(num_array_message, num_array_message2)):
-        array_message = [rand_ascii_str(array_message_length)]
-        array_message2 = [rand_ascii_str(array_message2_length)]
-        for text, text2 in zip(array_message, array_message2):
-            message_dict[phonenumber_tx].append(text)
-            message_dict[phonenumber_tx2].append(text2)
-            length = len(text)
-            length2 = len(text2)
-
-            ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
-                           phonenumber_tx, phonenumber_rx, length, text)
-            ad_tx2.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
-                           phonenumber_tx2, phonenumber_rx2, length2, text2)
-
-            try:
-                for ad in (ad_tx, ad_tx2):
-                    ad.send_keycode("BACK")
-                    if not getattr(ad, "messaging_droid", None):
-                        ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                        ad.messaging_ed.start()
-                    else:
-                        try:
-                            if not ad.messaging_droid.is_live:
-                                ad.messaging_droid, ad.messaging_ed = \
-                                    ad.get_droid()
-                                ad.messaging_ed.start()
-                            else:
-                                ad.messaging_ed.clear_all_events()
-                            ad.messaging_droid.logI(
-                                "Start sms_send_receive_verify_for_subscription"
-                                " test")
-                        except Exception:
-                            ad.log.info("Create new sl4a session for messaging")
-                            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                            ad.messaging_ed.start()
-
-                ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
-                ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
-                ad_tx2.messaging_ed.clear_events(EventSmsSentSuccess)
-                ad_tx2.messaging_ed.clear_events(EventSmsSentFailure)
-
-                if index < num_array_message and index < num_array_message2:
-                    ad_tx.messaging_droid.logI(
-                        "Sending SMS of length %s" % length)
-                    ad_tx2.messaging_droid.logI(
-                        "Sending SMS of length %s" % length2)
-                    tasks = [
-                        (ad_tx.messaging_droid.smsSendTextMessage,
-                        (phonenumber_rx, text, True)),
-                        (ad_tx2.messaging_droid.smsSendTextMessage,
-                        (phonenumber_rx2, text2, True))]
-                    multithread_func(log, tasks)
-                else:
-                    if index < num_array_message:
-                        ad_tx.messaging_droid.logI(
-                            "Sending SMS of length %s" % length)
-                        ad_tx.messaging_droid.smsSendTextMessage(
-                            phonenumber_rx, text, True)
-                    if index < num_array_message2:
-                        ad_tx2.messaging_droid.logI(
-                            "Sending SMS of length %s" % length2)
-                        ad_tx2.messaging_droid.smsSendTextMessage(
-                            phonenumber_rx2, text2, True)
-
-                try:
-                    if index < num_array_message and index < num_array_message2:
-                        tasks = [
-                            (ad_tx.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
-                                EventSmsSentSuccess,
-                                EventSmsSentFailure,
-                                EventSmsDeliverSuccess,
-                                EventSmsDeliverFailure),
-                                max_wait_time)),
-                            (ad_tx2.messaging_ed.pop_events, ("(%s|%s|%s|%s)" % (
-                                EventSmsSentSuccess,
-                                EventSmsSentFailure,
-                                EventSmsDeliverSuccess,
-                                EventSmsDeliverFailure),
-                                max_wait_time))
-                        ]
-                        results = run_multithread_func(log, tasks)
-                        res = True
-                        _ad = ad_tx
-                        for ad, events in [
-                            (ad_tx, results[0]), (ad_tx2, results[1])]:
-                            _ad = ad
-                            for event in events:
-                                ad.log.info("Got event %s", event["name"])
-                                if event["name"] == EventSmsSentFailure or \
-                                    event["name"] == EventSmsDeliverFailure:
-                                    if event.get("data") and \
-                                        event["data"].get("Reason"):
-                                        ad.log.error("%s with reason: %s",
-                                                        event["name"],
-                                                        event["data"]["Reason"])
-                                    res = False
-                                elif event["name"] == EventSmsSentSuccess or \
-                                    event["name"] == EventSmsDeliverSuccess:
-                                    break
-                        if not res:
-                            return False
-                    else:
-                        if index < num_array_message:
-                            result = ad_tx.messaging_ed.pop_events(
-                                "(%s|%s|%s|%s)" % (
-                                    EventSmsSentSuccess,
-                                    EventSmsSentFailure,
-                                    EventSmsDeliverSuccess,
-                                    EventSmsDeliverFailure),
-                                max_wait_time)
-                            res = True
-                            _ad = ad_tx
-                            for ad, events in [(ad_tx, result)]:
-                                _ad = ad
-                                for event in events:
-                                    ad.log.info("Got event %s", event["name"])
-                                    if event["name"] == EventSmsSentFailure or \
-                                        event["name"] == EventSmsDeliverFailure:
-                                        if event.get("data") and \
-                                            event["data"].get("Reason"):
-                                            ad.log.error(
-                                                "%s with reason: %s",
-                                                event["name"],
-                                                event["data"]["Reason"])
-                                        res = False
-                                    elif event["name"] == EventSmsSentSuccess \
-                                        or event["name"] == EventSmsDeliverSuccess:
-                                        break
-                            if not res:
-                                return False
-                        if index < num_array_message2:
-                            result = ad_tx2.messaging_ed.pop_events(
-                                "(%s|%s|%s|%s)" % (
-                                    EventSmsSentSuccess,
-                                    EventSmsSentFailure,
-                                    EventSmsDeliverSuccess,
-                                    EventSmsDeliverFailure),
-                                max_wait_time)
-                            res = True
-                            _ad = ad_tx2
-                            for ad, events in [(ad_tx2, result)]:
-                                _ad = ad
-                                for event in events:
-                                    ad.log.info("Got event %s", event["name"])
-                                    if event["name"] == EventSmsSentFailure or \
-                                        event["name"] == EventSmsDeliverFailure:
-                                        if event.get("data") and \
-                                            event["data"].get("Reason"):
-                                            ad.log.error(
-                                                "%s with reason: %s",
-                                                event["name"],
-                                                event["data"]["Reason"])
-                                        res = False
-                                    elif event["name"] == EventSmsSentSuccess \
-                                        or event["name"] == EventSmsDeliverSuccess:
-                                        break
-                            if not res:
-                                return False
-
-
-                except Empty:
-                    _ad.log.error("No %s or %s event for SMS of length %s.",
-                                    EventSmsSentSuccess, EventSmsSentFailure,
-                                    length)
-                    return False
-
-            except Exception as e:
-                log.error("Exception error %s", e)
-                raise
-
-    ad_rx.wait_for_boot_completion()
-    ad_rx.root_adb()
-    ad_rx.start_services(skip_setup_wizard=False)
-
-    output = ad_rx.adb.logcat("-t 1")
-    match = re.search(r"\d+-\d+\s\d+:\d+:\d+.\d+", output)
-    if match:
-        ad_rx.test_log_begin_time = match.group(0)
-
-    ad_rx.messaging_droid, ad_rx.messaging_ed = ad_rx.get_droid()
-    ad_rx.messaging_ed.start()
-    ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
-    time.sleep(1)  #sleep 100ms after starting event tracking
-
-    if not toggle_airplane_mode(log, ad_rx, False):
-        ad_rx.log.error("Failed to disable Airplane Mode")
-        return False
-
-    res = True
-    try:
-        if not wait_for_matching_multiple_sms(log,
-                ad_rx,
-                phonenumber_tx,
-                phonenumber_tx2,
-                messages=message_dict,
-                max_wait_time=max_wait_time):
-            res =  False
-    except Exception as e:
-        log.error("Exception error %s", e)
-        raise
-    finally:
-        ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
-
-    return res
-
-
-def wait_for_matching_mt_sms_in_collision(log,
-                          ad_rx,
-                          phonenumber_tx,
-                          phonenumber_tx2,
-                          text,
-                          text2,
-                          allow_multi_part_long_sms=True,
-                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-
-    if not allow_multi_part_long_sms:
-        try:
-            ad_rx.messaging_ed.wait_for_event(
-                EventSmsReceived,
-                is_sms_in_collision_match,
-                max_wait_time,
-                phonenumber_tx,
-                phonenumber_tx2,
-                text,
-                text2)
-            ad_rx.log.info("Got event %s", EventSmsReceived)
-            return True
-        except Empty:
-            ad_rx.log.error("No matched SMS received event.")
-            return False
-    else:
-        try:
-            received_sms = ''
-            received_sms2 = ''
-            remaining_text = text
-            remaining_text2 = text2
-            while (remaining_text != '' or remaining_text2 != ''):
-                event = ad_rx.messaging_ed.wait_for_event(
-                    EventSmsReceived,
-                    is_sms_in_collision_partial_match,
-                    max_wait_time,
-                    phonenumber_tx,
-                    phonenumber_tx2,
-                    remaining_text,
-                    remaining_text2)
-                event_text = event['data']['Text'].split(")")[-1].strip()
-                event_text_length = len(event_text)
-
-                if event_text in remaining_text:
-                    ad_rx.log.info("Got event %s of text length %s from %s",
-                                   EventSmsReceived, event_text_length,
-                                   phonenumber_tx)
-                    remaining_text = remaining_text[event_text_length:]
-                    received_sms += event_text
-                elif event_text in remaining_text2:
-                    ad_rx.log.info("Got event %s of text length %s from %s",
-                                   EventSmsReceived, event_text_length,
-                                   phonenumber_tx2)
-                    remaining_text2 = remaining_text2[event_text_length:]
-                    received_sms2 += event_text
-
-            ad_rx.log.info("Received SMS of length %s", len(received_sms))
-            ad_rx.log.info("Received SMS of length %s", len(received_sms2))
-            return True
-        except Empty:
-            ad_rx.log.error(
-                "Missing SMS received event.")
-            if received_sms != '':
-                ad_rx.log.error(
-                    "Only received partial matched SMS of length %s from %s",
-                    len(received_sms), phonenumber_tx)
-            if received_sms2 != '':
-                ad_rx.log.error(
-                    "Only received partial matched SMS of length %s from %s",
-                    len(received_sms2), phonenumber_tx2)
-            return False
-
-
-def wait_for_matching_mt_sms_in_collision_with_mo_sms(log,
-                          ad_rx,
-                          ad_rx2,
-                          phonenumber_tx,
-                          phonenumber_tx2,
-                          text,
-                          text2,
-                          allow_multi_part_long_sms=True,
-                          max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION):
-
-    if not allow_multi_part_long_sms:
-        result = True
-        try:
-            ad_rx.messaging_ed.wait_for_call_offhook_event(
-                EventSmsReceived,
-                is_sms_match,
-                max_wait_time,
-                phonenumber_tx,
-                text)
-            ad_rx.log.info("Got event %s", EventSmsReceived)
-        except Empty:
-            ad_rx.log.error("No matched SMS received event.")
-            result = False
-
-        try:
-            ad_rx2.messaging_ed.wait_for_call_offhook_event(
-                EventSmsReceived,
-                is_sms_match,
-                max_wait_time,
-                phonenumber_tx2,
-                text2)
-            ad_rx2.log.info("Got event %s", EventSmsReceived)
-        except Empty:
-            ad_rx2.log.error("No matched SMS received event.")
-            result = False
-
-        return result
-    else:
-        result = True
-        try:
-            received_sms = ''
-            remaining_text = text
-            while remaining_text != '':
-                event = ad_rx.messaging_ed.wait_for_event(
-                    EventSmsReceived, is_sms_partial_match, max_wait_time,
-                    phonenumber_tx, remaining_text)
-                event_text = event['data']['Text'].split(")")[-1].strip()
-                event_text_length = len(event_text)
-
-                if event_text in remaining_text:
-                    ad_rx.log.info("Got event %s of text length %s from %s",
-                                   EventSmsReceived, event_text_length,
-                                   phonenumber_tx)
-                    remaining_text = remaining_text[event_text_length:]
-                    received_sms += event_text
-
-            ad_rx.log.info("Received SMS of length %s", len(received_sms))
-        except Empty:
-            ad_rx.log.error(
-                "Missing SMS received event.")
-            if received_sms != '':
-                ad_rx.log.error(
-                    "Only received partial matched SMS of length %s from %s",
-                    len(received_sms), phonenumber_tx)
-            result = False
-
-        try:
-            received_sms2 = ''
-            remaining_text2 = text2
-            while remaining_text2 != '':
-                event2 = ad_rx2.messaging_ed.wait_for_event(
-                    EventSmsReceived, is_sms_partial_match, max_wait_time,
-                    phonenumber_tx2, remaining_text2)
-                event_text2 = event2['data']['Text'].split(")")[-1].strip()
-                event_text_length2 = len(event_text2)
-
-                if event_text2 in remaining_text2:
-                    ad_rx2.log.info("Got event %s of text length %s from %s",
-                                   EventSmsReceived, event_text_length2,
-                                   phonenumber_tx2)
-                    remaining_text2 = remaining_text2[event_text_length2:]
-                    received_sms2 += event_text2
-
-            ad_rx2.log.info("Received SMS of length %s", len(received_sms2))
-        except Empty:
-            ad_rx2.log.error(
-                "Missing SMS received event.")
-            if received_sms2 != '':
-                ad_rx2.log.error(
-                    "Only received partial matched SMS of length %s from %s",
-                    len(received_sms2), phonenumber_tx2)
-            result = False
-
-        return result
-
-
-def wait_for_matching_multiple_sms(log,
-                        ad_rx,
-                        phonenumber_tx,
-                        phonenumber_tx2,
-                        messages={},
-                        allow_multi_part_long_sms=True,
-                        max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-
-    if not allow_multi_part_long_sms:
-        try:
-            ad_rx.messaging_ed.wait_for_event(
-                EventSmsReceived,
-                is_sms_match_among_multiple_sms,
-                max_wait_time,
-                phonenumber_tx,
-                phonenumber_tx2,
-                messages[phonenumber_tx],
-                messages[phonenumber_tx2])
-            ad_rx.log.info("Got event %s", EventSmsReceived)
-            return True
-        except Empty:
-            ad_rx.log.error("No matched SMS received event.")
-            return False
-    else:
-        all_msgs = []
-        for tx, msgs in messages.items():
-            for msg in msgs:
-                all_msgs.append([tx, msg, msg, ''])
-
-        all_msgs_copy = all_msgs.copy()
-
-        try:
-            while (all_msgs != []):
-                event = ad_rx.messaging_ed.wait_for_event(
-                    EventSmsReceived,
-                    is_sms_partial_match_among_multiple_sms,
-                    max_wait_time,
-                    phonenumber_tx,
-                    phonenumber_tx2,
-                    messages[phonenumber_tx],
-                    messages[phonenumber_tx2])
-                event_text = event['data']['Text'].split(")")[-1].strip()
-                event_text_length = len(event_text)
-
-                for msg in all_msgs_copy:
-                    if event_text in msg[2]:
-                        ad_rx.log.info("Got event %s of text length %s from %s",
-                                       EventSmsReceived, event_text_length,
-                                       msg[0])
-                        msg[2] = msg[2][event_text_length:]
-                        msg[3] += event_text
-
-                        if msg[2] == "":
-                            all_msgs.remove(msg)
-
-            ad_rx.log.info("Received all SMS' sent when power-off.")
-        except Empty:
-            ad_rx.log.error(
-                "Missing SMS received event.")
-
-            for msg in all_msgs_copy:
-                if msg[3] != '':
-                    ad_rx.log.error(
-                        "Only received partial matched SMS of length %s from %s",
-                        len(msg[3]), msg[0])
-            return False
-
-        return True
-
-
-def is_sms_in_collision_match(
-    event, phonenumber_tx, phonenumber_tx2, text, text2):
-    event_text = event['data']['Text'].strip()
-    if event_text.startswith("("):
-        event_text = event_text.split(")")[-1]
-
-    for phonenumber, txt in [[phonenumber_tx, text], [phonenumber_tx2, text2]]:
-        if check_phone_number_match(
-            event['data']['Sender'], phonenumber) and txt.startswith(event_text):
-            return True
-    return False
-
-
-def is_sms_in_collision_partial_match(
-    event, phonenumber_tx, phonenumber_tx2, text, text2):
-    for phonenumber, txt in [[phonenumber_tx, text], [phonenumber_tx2, text2]]:
-        if check_phone_number_match(
-            event['data']['Sender'], phonenumber) and \
-                event['data']['Text'].strip() == txt:
-            return True
-    return False
-
-
-def is_sms_match_among_multiple_sms(
-    event, phonenumber_tx, phonenumber_tx2, texts=[], texts2=[]):
-    for txt in texts:
-        if check_phone_number_match(
-            event['data']['Sender'], phonenumber_tx) and \
-                event['data']['Text'].strip() == txt:
-                return True
-
-    for txt in texts2:
-        if check_phone_number_match(
-            event['data']['Sender'], phonenumber_tx2) and \
-                event['data']['Text'].strip() == txt:
-                return True
-
-    return False
-
 
-def is_sms_partial_match_among_multiple_sms(
-    event, phonenumber_tx, phonenumber_tx2, texts=[], texts2=[]):
-    event_text = event['data']['Text'].strip()
-    if event_text.startswith("("):
-        event_text = event_text.split(")")[-1]
 
-    for txt in texts:
-        if check_phone_number_match(
-            event['data']['Sender'], phonenumber_tx) and \
-                txt.startswith(event_text):
-                return True
 
-    for txt in texts2:
-        if check_phone_number_match(
-            event['data']['Sender'], phonenumber_tx2) and \
-                txt.startswith(event_text):
-                return True
 
-    return False
 
 
 def set_time_sync_from_network(ad, action):
@@ -10962,31 +9864,6 @@
     return get_value
 
 
-def wait_for_sending_sms(ad_tx, max_wait_time=MAX_WAIT_TIME_SMS_RECEIVE):
-    try:
-        events = ad_tx.messaging_ed.pop_events(
-            "(%s|%s|%s|%s)" %
-            (EventSmsSentSuccess, EventSmsSentFailure,
-                EventSmsDeliverSuccess,
-                EventSmsDeliverFailure), max_wait_time)
-        for event in events:
-            ad_tx.log.info("Got event %s", event["name"])
-            if event["name"] == EventSmsSentFailure or \
-                event["name"] == EventSmsDeliverFailure:
-                if event.get("data") and event["data"].get("Reason"):
-                    ad_tx.log.error("%s with reason: %s",
-                                    event["name"],
-                                    event["data"]["Reason"])
-                return False
-            elif event["name"] == EventSmsSentSuccess or \
-                event["name"] == EventSmsDeliverSuccess:
-                return True
-    except Empty:
-        ad_tx.log.error("No %s or %s event for SMS.",
-                        EventSmsSentSuccess, EventSmsSentFailure)
-        return False
-
-
 def wait_for_call_end(
         log,
         ad_caller,
@@ -11047,242 +9924,6 @@
     return tel_result_wrapper
 
 
-def voice_call_in_collision_with_mt_sms_msim(
-        log,
-        ad_primary,
-        ad_sms,
-        ad_voice,
-        sms_subid_ad_primary,
-        sms_subid_ad_sms,
-        voice_subid_ad_primary,
-        voice_subid_ad_voice,
-        array_message,
-        ad_hangup=None,
-        verify_caller_func=None,
-        verify_callee_func=None,
-        call_direction="mo",
-        wait_time_in_call=WAIT_TIME_IN_CALL,
-        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
-        dialing_number_length=None,
-        video_state=None):
-
-    ad_tx = ad_sms
-    ad_rx = ad_primary
-    subid_tx = sms_subid_ad_sms
-    subid_rx = sms_subid_ad_primary
-
-    if call_direction == "mo":
-        ad_caller = ad_primary
-        ad_callee = ad_voice
-        subid_caller = voice_subid_ad_primary
-        subid_callee = voice_subid_ad_voice
-    elif call_direction == "mt":
-        ad_callee = ad_primary
-        ad_caller = ad_voice
-        subid_callee = voice_subid_ad_primary
-        subid_caller = voice_subid_ad_voice
-
-
-    phonenumber_tx = ad_tx.telephony['subscription'][subid_tx]['phone_num']
-    phonenumber_rx = ad_rx.telephony['subscription'][subid_rx]['phone_num']
-
-    tel_result_wrapper = TelResultWrapper(CallResult('SUCCESS'))
-
-    for ad in (ad_tx, ad_rx):
-        ad.send_keycode("BACK")
-        if not getattr(ad, "messaging_droid", None):
-            ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-            ad.messaging_ed.start()
-        else:
-            try:
-                if not ad.messaging_droid.is_live:
-                    ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                    ad.messaging_ed.start()
-                else:
-                    ad.messaging_ed.clear_all_events()
-            except Exception:
-                ad.log.info("Create new sl4a session for messaging")
-                ad.messaging_droid, ad.messaging_ed = ad.get_droid()
-                ad.messaging_ed.start()
-
-    if not verify_caller_func:
-        verify_caller_func = is_phone_in_call
-    if not verify_callee_func:
-        verify_callee_func = is_phone_in_call
-
-    caller_number = ad_caller.telephony['subscription'][subid_caller][
-        'phone_num']
-    callee_number = ad_callee.telephony['subscription'][subid_callee][
-        'phone_num']
-    if dialing_number_length:
-        skip_test = False
-        trunc_position = 0 - int(dialing_number_length)
-        try:
-            caller_area_code = caller_number[:trunc_position]
-            callee_area_code = callee_number[:trunc_position]
-            callee_dial_number = callee_number[trunc_position:]
-        except:
-            skip_test = True
-        if caller_area_code != callee_area_code:
-            skip_test = True
-        if skip_test:
-            msg = "Cannot make call from %s to %s by %s digits" % (
-                caller_number, callee_number, dialing_number_length)
-            ad_caller.log.info(msg)
-            raise signals.TestSkip(msg)
-        else:
-            callee_number = callee_dial_number
-
-    msg = "Call from %s to %s" % (caller_number, callee_number)
-    if video_state:
-        msg = "Video %s" % msg
-        video = True
-    else:
-        video = False
-    if ad_hangup:
-        msg = "%s for duration of %s seconds" % (msg, wait_time_in_call)
-    ad_caller.log.info(msg)
-
-    for ad in (ad_caller, ad_callee):
-        call_ids = ad.droid.telecomCallGetCallIds()
-        setattr(ad, "call_ids", call_ids)
-        if call_ids:
-            ad.log.info("Pre-exist CallId %s before making call", call_ids)
-
-    ad_caller.ed.clear_events(EventCallStateChanged)
-    call_begin_time = get_device_epoch_time(ad)
-    ad_caller.droid.telephonyStartTrackingCallStateForSubscription(subid_caller)
-
-    for text in array_message:
-        length = len(text)
-        ad_tx.log.info("Sending SMS from %s to %s, len: %s, content: %s.",
-                       phonenumber_tx, phonenumber_rx, length, text)
-        try:
-            ad_rx.messaging_ed.clear_events(EventSmsReceived)
-            ad_tx.messaging_ed.clear_events(EventSmsSentSuccess)
-            ad_tx.messaging_ed.clear_events(EventSmsSentFailure)
-            ad_rx.messaging_droid.smsStartTrackingIncomingSmsMessage()
-            time.sleep(1)  #sleep 100ms after starting event tracking
-            ad_tx.messaging_droid.logI("Sending SMS of length %s" % length)
-            ad_rx.messaging_droid.logI("Expecting SMS of length %s" % length)
-            ad_caller.log.info("Make a phone call to %s", callee_number)
-
-            tasks = [
-                (ad_tx.messaging_droid.smsSendTextMessage,
-                (phonenumber_rx, text, True)),
-                (ad_caller.droid.telecomCallNumber,
-                (callee_number, video))]
-
-            run_multithread_func(log, tasks)
-
-            try:
-                # Verify OFFHOOK state
-                if not wait_for_call_offhook_for_subscription(
-                        log,
-                        ad_caller,
-                        subid_caller,
-                        event_tracking_started=True):
-                    ad_caller.log.info(
-                        "sub_id %s not in call offhook state", subid_caller)
-                    last_call_drop_reason(ad_caller, begin_time=call_begin_time)
-
-                    ad_caller.log.error("Initiate call failed.")
-                    tel_result_wrapper.result_value = CallResult(
-                                                        'INITIATE_FAILED')
-                    return tel_result_wrapper
-                else:
-                    ad_caller.log.info("Caller initate call successfully")
-            finally:
-                ad_caller.droid.telephonyStopTrackingCallStateChangeForSubscription(
-                    subid_caller)
-                if incall_ui_display == INCALL_UI_DISPLAY_FOREGROUND:
-                    ad_caller.droid.telecomShowInCallScreen()
-                elif incall_ui_display == INCALL_UI_DISPLAY_BACKGROUND:
-                    ad_caller.droid.showHomeScreen()
-
-            if not wait_and_answer_call_for_subscription(
-                    log,
-                    ad_callee,
-                    subid_callee,
-                    incoming_number=caller_number,
-                    caller=ad_caller,
-                    incall_ui_display=incall_ui_display,
-                    video_state=video_state):
-                ad_callee.log.error("Answer call fail.")
-                tel_result_wrapper.result_value = CallResult(
-                    'NO_RING_EVENT_OR_ANSWER_FAILED')
-                return tel_result_wrapper
-            else:
-                ad_callee.log.info("Callee answered the call successfully")
-
-            for ad, call_func in zip([ad_caller, ad_callee],
-                                     [verify_caller_func, verify_callee_func]):
-                call_ids = ad.droid.telecomCallGetCallIds()
-                new_call_ids = set(call_ids) - set(ad.call_ids)
-                if not new_call_ids:
-                    ad.log.error(
-                        "No new call ids are found after call establishment")
-                    ad.log.error("telecomCallGetCallIds returns %s",
-                                 ad.droid.telecomCallGetCallIds())
-                    tel_result_wrapper.result_value = CallResult(
-                                                        'NO_CALL_ID_FOUND')
-                for new_call_id in new_call_ids:
-                    if not wait_for_in_call_active(ad, call_id=new_call_id):
-                        tel_result_wrapper.result_value = CallResult(
-                            'CALL_STATE_NOT_ACTIVE_DURING_ESTABLISHMENT')
-                    else:
-                        ad.log.info(
-                            "callProperties = %s",
-                            ad.droid.telecomCallGetProperties(new_call_id))
-
-                if not ad.droid.telecomCallGetAudioState():
-                    ad.log.error("Audio is not in call state")
-                    tel_result_wrapper.result_value = CallResult(
-                        'AUDIO_STATE_NOT_INCALL_DURING_ESTABLISHMENT')
-
-                if call_func(log, ad):
-                    ad.log.info("Call is in %s state", call_func.__name__)
-                else:
-                    ad.log.error("Call is not in %s state, voice in RAT %s",
-                                 call_func.__name__,
-                                 ad.droid.telephonyGetCurrentVoiceNetworkType())
-                    tel_result_wrapper.result_value = CallResult(
-                        'CALL_DROP_OR_WRONG_STATE_DURING_ESTABLISHMENT')
-            if not tel_result_wrapper:
-                return tel_result_wrapper
-
-            if not wait_for_sending_sms(
-                ad_tx,
-                max_wait_time=MAX_WAIT_TIME_SMS_SENT_SUCCESS_IN_COLLISION):
-                return False
-
-            tasks = [
-                (wait_for_matching_sms,
-                (log, ad_rx, phonenumber_tx, text,
-                MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION, True)),
-                (wait_for_call_end,
-                (log, ad_caller, ad_callee, ad_hangup, verify_caller_func,
-                    verify_callee_func, call_begin_time, 5, tel_result_wrapper,
-                    WAIT_TIME_IN_CALL))]
-
-            results = run_multithread_func(log, tasks)
-
-            if not results[0]:
-                ad_rx.log.error("No matching received SMS of length %s.",
-                                length)
-                return False
-
-            tel_result_wrapper = results[1]
-
-        except Exception as e:
-            log.error("Exception error %s", e)
-            raise
-        finally:
-            ad_rx.messaging_droid.smsStopTrackingIncomingSmsMessage()
-
-    return tel_result_wrapper
-
-
 def change_voice_subid_temporarily(ad, sub_id, state_check_func, params=None):
     result = False
     voice_sub_id_changed = False
diff --git a/acts_tests/acts_contrib/test_utils/wifi/OWNERS b/acts_tests/acts_contrib/test_utils/wifi/OWNERS
index 00010b6..10e4214 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/OWNERS
+++ b/acts_tests/acts_contrib/test_utils/wifi/OWNERS
@@ -1,7 +1,5 @@
 bkleung@google.com
-dysu@google.com
-etancohen@google.com
 gmoturu@google.com
-rpius@google.com
-satk@google.com
 hsiuchangchen@google.com
+
+include platform/packages/modules/Wifi:/WIFI_OWNERS
diff --git a/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py
index 9f3ba7e..ab87f88 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py
@@ -55,7 +55,7 @@
         if hasattr(self, 'attenuators') and self.attenuators:
             for attenuator in self.attenuators:
                 attenuator.set_atten(0)
-        opt_param = ["pixel_models", "cnss_diag_file"]
+        opt_param = ["pixel_models", "cnss_diag_file", "country_code_file"]
         self.unpack_userparams(opt_param_names=opt_param)
         if hasattr(self, "cnss_diag_file"):
             if isinstance(self.cnss_diag_file, list):
@@ -68,6 +68,19 @@
             self.packet_logger = self.packet_capture[0]
             self.packet_logger.configure_monitor_mode("2G", self.packet_log_2g)
             self.packet_logger.configure_monitor_mode("5G", self.packet_log_5g)
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                wutils.wifi_test_device_init(ad)
+                if hasattr(self, "country_code_file"):
+                    if isinstance(self.country_code_file, list):
+                        self.country_code_file = self.country_code_file[0]
+                    if not os.path.isfile(self.country_code_file):
+                        self.country_code_file = os.path.join(
+                            self.user_params[Config.key_config_path.value],
+                            self.country_code_file)
+                    self.country_code = utils.load_config(
+                        self.country_code_file)["country"]
+                    wutils.set_wifi_country_code(ad, self.country_code)
 
     def setup_test(self):
         if (hasattr(self, "android_devices")
@@ -376,6 +389,8 @@
             self,
             channel_5g=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
             channel_2g=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
+            channel_5g_ap2=None,
+            channel_2g_ap2=None,
             ssid_length_2g=hostapd_constants.AP_SSID_LENGTH_2G,
             passphrase_length_2g=hostapd_constants.AP_PASSPHRASE_LENGTH_2G,
             ssid_length_5g=hostapd_constants.AP_SSID_LENGTH_5G,
@@ -384,6 +399,7 @@
             hidden=False,
             same_ssid=False,
             open_network=False,
+            wpa1_network=False,
             wpa_network=False,
             wep_network=False,
             ent_network=False,
@@ -399,6 +415,8 @@
         Args:
             channel_5g: 5G channel to configure.
             channel_2g: 2G channel to configure.
+            channel_5g_ap2: 5G channel to configure on AP2.
+            channel_2g_ap2: 2G channel to configure on AP2.
             ssid_length_2g: Int, number of characters to use for 2G SSID.
             passphrase_length_2g: Int, length of password for 2G network.
             ssid_length_5g: Int, number of characters to use for 5G SSID.
@@ -418,8 +436,21 @@
         """
         if mirror_ap and ap_count == 1:
             raise ValueError("ap_count cannot be 1 if mirror_ap is True.")
+        if (channel_5g_ap2 or channel_2g_ap2) and ap_count == 1:
+            raise ValueError(
+                "ap_count cannot be 1 if channels of AP2 are provided.")
+        # we are creating a channel list for 2G and 5G bands. The list is of
+        # size 2 and this is based on the assumption that each testbed will have
+        # at most 2 APs.
+        if not channel_5g_ap2:
+            channel_5g_ap2 = channel_5g
+        if not channel_2g_ap2:
+            channel_2g_ap2 = channel_2g
+        channels_2g = [channel_2g, channel_2g_ap2]
+        channels_5g = [channel_5g, channel_5g_ap2]
 
         self.reference_networks = []
+        self.wpa1_networks = []
         self.wpa_networks = []
         self.wep_networks = []
         self.ent_networks = []
@@ -430,6 +461,17 @@
         self.bssid_map = []
         for i in range(ap_count):
             network_list = []
+            if wpa1_network:
+                wpa1_dict = self.get_psk_network(mirror_ap,
+                                                 self.wpa1_networks,
+                                                 hidden, same_ssid,
+                                                 ssid_length_2g, ssid_length_5g,
+                                                 passphrase_length_2g,
+                                                 passphrase_length_5g)
+                wpa1_dict[hostapd_constants.BAND_2G]["security"] = "psk"
+                wpa1_dict[hostapd_constants.BAND_5G]["security"] = "psk"
+                self.wpa1_networks.append(wpa1_dict)
+                network_list.append(wpa1_dict)
             if wpa_network:
                 wpa_dict = self.get_psk_network(mirror_ap,
                                                 self.reference_networks,
@@ -491,14 +533,15 @@
                 sae_dict[hostapd_constants.BAND_2G]["security"] = "sae"
                 sae_dict[hostapd_constants.BAND_5G]["security"] = "sae"
                 network_list.append(sae_dict)
-            self.access_points[i].configure_ap(network_list, channel_2g,
-                                               channel_5g)
+            self.access_points[i].configure_ap(network_list, channels_2g[i],
+                                               channels_5g[i])
             self.access_points[i].start_ap()
             self.bssid_map.append(
                 self.access_points[i].get_bssids_for_wifi_networks())
             if mirror_ap:
                 self.access_points[i + 1].configure_ap(network_list,
-                                                       channel_2g, channel_5g)
+                                                       channels_2g[i+1],
+                                                       channels_5g[i+1])
                 self.access_points[i + 1].start_ap()
                 self.bssid_map.append(
                     self.access_points[i + 1].get_bssids_for_wifi_networks())
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
index e87fc9f..c0c1075 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
@@ -150,4 +150,4 @@
                 wutils.get_cnss_diag_log(ad)
         for proc in self.tcpdump_proc:
             nutils.stop_tcpdump(proc[0], proc[1], self.test_name)
-        self.tcpdump_proc = []
\ No newline at end of file
+        self.tcpdump_proc = []
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
index d5a0381..f0f385b 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
@@ -97,6 +97,7 @@
 SESSION_CB_ON_MESSAGE_SENT = "WifiAwareSessionOnMessageSent"
 SESSION_CB_ON_MESSAGE_SEND_FAILED = "WifiAwareSessionOnMessageSendFailed"
 SESSION_CB_ON_MESSAGE_RECEIVED = "WifiAwareSessionOnMessageReceived"
+SESSION_CB_ON_SERVICE_LOST = "WifiAwareSessionOnServiceLost"
 
 # WifiAwareDiscoverySessionCallback events keys
 SESSION_CB_KEY_CB_ID = "callbackId"
@@ -112,6 +113,10 @@
 SESSION_CB_KEY_LATENCY_MS = "latencyMs"
 SESSION_CB_KEY_TIMESTAMP_MS = "timestampMs"
 SESSION_CB_KEY_DISTANCE_MM = "distanceMm"
+SESSION_CB_KEY_LOST_REASON = "lostReason"
+
+# WifiAwareDiscoverySessionCallback onServiceLost reason code
+REASON_PEER_NOT_VISIBLE = 1
 
 ######################################################
 # WifiAwareRangingListener events (RttManager.RttListener)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
index 222c6d8..4f22289 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
@@ -64,8 +64,26 @@
         ad.log.info('%sTimed out while waiting for %s', prefix, event_name)
         asserts.fail(event_name)
 
+def _filter_callbacks(event, expected_kv):
+    """
+    Helper method to use in |fail_on_event_with_keys| and
+    |wait_for_event_with_keys|
+    """
+    for expected_k, expected_v in expected_kv:
+        actual_v = event['data'][expected_k]
+        if isinstance(expected_v, dict) and isinstance(actual_v, dict):
+            # |expected_v| not a subset of |actual_v|
+            if not(expected_v.items() <= actual_v.items()):
+                return False
+        else:
+            if actual_v != expected_v:
+                return False
+    return True
 
-def wait_for_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT,
+
+def wait_for_event_with_keys(ad,
+                             event_name,
+                             timeout=EVENT_TIMEOUT,
                              *keyvalues):
     """Wait for the specified event contain the key/value pairs or timeout
 
@@ -73,23 +91,16 @@
     ad: The android device
     event_name: The event to wait on
     timeout: Number of seconds to wait
-    keyvalues: (kay, value) pairs
+    keyvalues: Expected (key, value) pairs. If the value for a key is a dict,
+               then this will perform subset matching for that key.
   Returns:
     The event (if available)
   """
-
-    def filter_callbacks(event, keyvalues):
-        for keyvalue in keyvalues:
-            key, value = keyvalue
-            if event['data'][key] != value:
-                return False
-        return True
-
     prefix = ''
     if hasattr(ad, 'pretty_name'):
         prefix = '[%s] ' % ad.pretty_name
     try:
-        event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+        event = ad.ed.wait_for_event(event_name, _filter_callbacks, timeout,
                                      keyvalues)
         ad.log.info('%s%s: %s', prefix, event_name, event['data'])
         return event
@@ -128,21 +139,14 @@
     ad: The android device
     event_name: The event to wait on
     timeout: Number of seconds to wait
-    keyvalues: (kay, value) pairs
+    keyvalues: Expected (key, value) pairs. If the value for a key is a dict,
+               then this will perform subset matching for that key.
   """
-
-    def filter_callbacks(event, keyvalues):
-        for keyvalue in keyvalues:
-            key, value = keyvalue
-            if event['data'][key] != value:
-                return False
-        return True
-
     prefix = ''
     if hasattr(ad, 'pretty_name'):
         prefix = '[%s] ' % ad.pretty_name
     try:
-        event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+        event = ad.ed.wait_for_event(event_name, _filter_callbacks, timeout,
                                      keyvalues)
         ad.log.info('%sReceived unwanted %s: %s', prefix, event_name,
                     event['data'])
@@ -286,8 +290,6 @@
 
     if limited_cb is None:
         limited_cb = {}
-    # add callbacks which should never be called
-    limited_cb[aconsts.CB_EV_MATCH_EXPIRED] = 0
 
     fail = False
     for cb_event in limited_cb.keys():
@@ -410,10 +412,10 @@
   """
     out = device.adb.shell("ifconfig %s" % interface)
     res = re.match(".* HWaddr (\S+).*", out, re.S)
-    asserts.assert_true(
-        res,
-        'Unable to obtain MAC address for interface %s' % interface,
-        extras=out)
+    asserts.assert_true(res,
+                        'Unable to obtain MAC address for interface %s' %
+                        interface,
+                        extras=out)
     return res.group(1).upper().replace(':', '')
 
 
@@ -454,8 +456,9 @@
         port_to_use = port
         if port == 0:
             port_to_use = dut_s.droid.getTcpServerSocketPort(server_sock)
-        sock_c, sock_s = sutils.open_connect_socket(
-            dut_c, dut_s, ipv6_c, ipv6_s, 0, port_to_use, server_sock)
+        sock_c, sock_s = sutils.open_connect_socket(dut_c, dut_s, ipv6_c,
+                                                    ipv6_s, 0, port_to_use,
+                                                    server_sock)
     except:
         return False
     finally:
@@ -490,6 +493,44 @@
     return latency_result
 
 
+def reset_device_parameters(ad):
+    """Reset device configurations.
+
+    Args:
+      ad: device to be reset
+    """
+    ad.adb.shell("cmd wifiaware reset")
+
+
+def reset_device_statistics(ad):
+    """Reset device statistics.
+
+    Args:
+        ad: device to be reset
+    """
+    ad.adb.shell("cmd wifiaware native_cb get_cb_count --reset")
+
+
+def set_power_mode_parameters(ad, power_mode):
+    """Set device power mode.
+
+    Set the power configuration DW parameters for the device based on any
+    configuration overrides (if provided)
+
+    Args:
+        ad: android device
+        power_mode: Desired power mode (INTERACTIVE or NON_INTERACTIVE)
+    """
+    if power_mode == "INTERACTIVE":
+        config_settings_high_power(ad)
+    elif power_mode == "NON_INTERACTIVE":
+        config_settings_low_power(ad)
+    else:
+        asserts.assert_false(
+            "The 'aware_default_power_mode' configuration must be INTERACTIVE or "
+            "NON_INTERACTIVE")
+
+
 #########################################################
 # Aware primitives
 #########################################################
@@ -537,8 +578,8 @@
     name: One of the power settings from 'wifiaware set-power'.
     value: An integer.
   """
-    device.adb.shell(
-        "cmd wifiaware native_api set-power %s %s %d" % (mode, name, value))
+    device.adb.shell("cmd wifiaware native_api set-power %s %s %d" %
+                     (mode, name, value))
 
 
 def configure_mac_random_interval(device, interval_sec):
@@ -550,8 +591,9 @@
     interval_sec: The MAC randomization interval in seconds. A value of 0
                   disables all randomization.
   """
-    device.adb.shell("cmd wifiaware native_api set mac_random_interval_sec %d"
-                     % interval_sec)
+    device.adb.shell(
+        "cmd wifiaware native_api set mac_random_interval_sec %d" %
+        interval_sec)
 
 
 def configure_ndp_allow_any_override(device, override_api_check):
@@ -843,8 +885,12 @@
                            the two devices.
   """
     (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
-     peer_id_on_pub) = create_discovery_pair(
-         p_dut, s_dut, p_config, s_config, device_startup_offset, msg_id=9999)
+     peer_id_on_pub) = create_discovery_pair(p_dut,
+                                             s_dut,
+                                             p_config,
+                                             s_config,
+                                             device_startup_offset,
+                                             msg_id=9999)
 
     # Publisher: request network
     p_req_key = request_network(
@@ -1001,9 +1047,9 @@
     # to execute the data-path request)
     time.sleep(WAIT_FOR_CLUSTER)
 
-    (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
-     init_ipv6, resp_ipv6) = create_oob_ndp_on_sessions(
-         init_dut, resp_dut, init_id, init_mac, resp_id, resp_mac)
+    (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+     resp_ipv6) = create_oob_ndp_on_sessions(init_dut, resp_dut, init_id,
+                                             init_mac, resp_id, resp_mac)
 
     return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
             init_ipv6, resp_ipv6)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
index 73fb033..a20936f 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
@@ -90,7 +90,8 @@
         """Returns name of the sniffer dump file."""
         remote_file_name = 'sniffer_dump.{}'.format(
             self.sniffer_output_file_type)
-        remote_dump_path = posixpath.join(posixpath.sep, 'tmp', remote_file_name)
+        remote_dump_path = posixpath.join(posixpath.sep, 'tmp',
+                                          remote_file_name)
         return remote_dump_path
 
     def _get_full_file_path(self, tag=None):
@@ -356,7 +357,7 @@
         try:
             self.log.debug('Killing sniffer with SIGKILL.')
             self._sniffer_server.run('sudo kill -9 {}'.format(
-                    str(self.sniffer_proc_pid)))
+                str(self.sniffer_proc_pid)))
         except:
             self.log.debug('Sniffer process may have stopped succesfully.')
 
@@ -532,6 +533,9 @@
                 (140, 142, 144): 142,
                 (149, 151, 153): 151,
                 (157, 159, 161): 159
+            },
+            160: {
+                (36, 38, 40): 50
             }
         }
 
@@ -568,5 +572,5 @@
             network: dictionary of network credentials; SSID and password.
         """
 
-        self.log.debug('Connecting to network {}'.format(network['SSID']))
+        self.log.debug('Setting monitor mode on Ch {}, bw {}'.format(chan, bw))
         self.set_monitor_mode(chan, bw)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py
index f3e859f..c0b18ae 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py
@@ -156,4 +156,4 @@
         dut.reboot()
         time.sleep(WAIT_TIME)
         out = dut.adb.shell("ifconfig p2p0")
-        return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
\ No newline at end of file
+        return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py
index 87f4059..32e4d2d 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py
@@ -25,6 +25,8 @@
 ######################################################
 
 DEFAULT_TIMEOUT = 30
+DEFAULT_CONNECT_SLEEPTIME = 3
+DEFAULT_POLLING_SLEEPTIME = 1
 DEFAULT_SLEEPTIME = 5
 DEFAULT_FUNCTION_SWITCH_TIME = 10
 DEFAULT_SERVICE_WAITING_TIME = 20
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
old mode 100644
new mode 100755
index 006529a..a96b287
--- a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
@@ -135,6 +135,54 @@
          ad_group_info_event['data']['Interface']))
     return ad_group_info_event['data']
 
+def is_ongoing_peer_ready(peerConfig, waitForPin):
+    """Check whether the peer config is ready
+
+    Args:
+        peerConfig: the ongoing config
+        waitForPin: this config needs key or not
+    Return:
+        true for ready; false otherwise.
+    """
+    if peerConfig is None:
+        return False
+    if not peerConfig['data'][WifiP2PEnums.WifiP2pConfig.DEVICEADDRESS_KEY]:
+        return False
+    if not waitForPin:
+        return True
+    if WifiP2PEnums.WpsInfo.WPS_PIN_KEY in peerConfig['data'][
+        WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY]:
+        return True
+    return False
+
+def wait_for_ongoing_peer_ready(ad, waitForPin, maxPollingCount):
+    """wait for the ongoing peer data ready
+
+    Args:
+        ad: The android device
+        waitForPin: this config needs key or not
+        maxPollingCount: the max polling count
+    Return:
+        the ongoing peer config
+    """
+    ad_peerConfig = None
+    ad.log.debug("%s is waiting for the ongoing peer, max polling count %s"
+        % (ad.name, maxPollingCount))
+    while maxPollingCount > 0:
+        ad.droid.requestP2pPeerConfigure()
+        ad_peerConfig = ad.ed.pop_event(
+            p2pconsts.ONGOING_PEER_INFO_AVAILABLE_EVENT,
+            p2pconsts.DEFAULT_TIMEOUT)
+        maxPollingCount -= 1
+        if is_ongoing_peer_ready(ad_peerConfig, waitForPin):
+            break
+        ad.log.debug("%s is not ready for next step" % (ad.name))
+        time.sleep(p2pconsts.DEFAULT_POLLING_SLEEPTIME)
+    asserts.assert_true(
+        ad_peerConfig['data'][WifiP2PEnums.WifiP2pConfig.DEVICEADDRESS_KEY],
+        "DUT %s does not receive the request." % (ad.name))
+    ad.log.debug(ad_peerConfig['data'])
+    return ad_peerConfig
 
 #trigger p2p connect to ad2 from ad1
 def p2p_connect(ad1,
@@ -160,6 +208,7 @@
         if go_ad is None:
             go_ad = ad1
         find_p2p_device(ad1, ad2)
+        # GO might be another peer, so ad2 needs to find it first.
         find_p2p_group_owner(ad2, go_ad)
     elif p2p_connect_type == p2pconsts.P2P_CONNECT_JOIN:
         find_p2p_group_owner(ad1, ad2)
@@ -175,23 +224,19 @@
     ad1.droid.wifiP2pConnect(wifi_p2p_config)
     ad1.ed.pop_event(p2pconsts.CONNECT_SUCCESS_EVENT,
                      p2pconsts.DEFAULT_TIMEOUT)
-    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
     if not isReconnect:
-        ad1.droid.requestP2pPeerConfigure()
-        ad1_peerConfig = ad1.ed.pop_event(
-            p2pconsts.ONGOING_PEER_INFO_AVAILABLE_EVENT,
-            p2pconsts.DEFAULT_TIMEOUT)
-        ad1.log.debug(ad1_peerConfig['data'])
-        ad2.droid.requestP2pPeerConfigure()
-        ad2_peerConfig = ad2.ed.pop_event(
-            p2pconsts.ONGOING_PEER_INFO_AVAILABLE_EVENT,
-            p2pconsts.DEFAULT_TIMEOUT)
-        ad2.log.debug(ad2_peerConfig['data'])
+        # ad1 is the initiator, it should be ready soon.
+        ad1_peerConfig = wait_for_ongoing_peer_ready(ad1,
+            wpsSetup == WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY, 6)
+        # auto-join tries 10 times to find groups, and
+        # one round takes 2 - 3 seconds.
+        ad2_peerConfig = wait_for_ongoing_peer_ready(ad2,
+            wpsSetup == WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD, 31)
         if wpsSetup == WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY:
             asserts.assert_true(
                 WifiP2PEnums.WpsInfo.WPS_PIN_KEY in ad1_peerConfig['data'][
                     WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY],
-                "Can't get pin value")
+                "Can't get display pin value")
             ad2_peerConfig['data'][WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
                 WifiP2PEnums.WpsInfo.WPS_PIN_KEY] = ad1_peerConfig['data'][
                     WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
@@ -204,7 +249,7 @@
             asserts.assert_true(
                 WifiP2PEnums.WpsInfo.WPS_PIN_KEY in ad2_peerConfig['data'][
                     WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY],
-                "Can't get pin value")
+                "Can't get keypad pin value")
             ad1_peerConfig['data'][WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
                 WifiP2PEnums.WpsInfo.WPS_PIN_KEY] = ad2_peerConfig['data'][
                     WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
@@ -212,8 +257,6 @@
             ad1.droid.setP2pPeerConfigure(ad1_peerConfig['data'])
             ad1.ed.pop_event(p2pconsts.ONGOING_PEER_SET_SUCCESS_EVENT,
                              p2pconsts.DEFAULT_TIMEOUT)
-            #Need to Accept first in ad1 to avoid connect time out in ad2,
-            #the timeout just 1 sec in ad2
             ad1.droid.wifiP2pAcceptConnection()
             time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
             ad2.droid.wifiP2pConfirmConnection()
@@ -274,6 +317,7 @@
     ad1.droid.wifiP2pDiscoverPeers()
     ad2.droid.wifiP2pDiscoverPeers()
     p2p_find_result = False
+    ad1.ed.clear_events(p2pconsts.PEER_AVAILABLE_EVENT)
     while not p2p_find_result:
         ad1_event = ad1.ed.pop_event(p2pconsts.PEER_AVAILABLE_EVENT,
                                      p2pconsts.P2P_FIND_TIMEOUT)
@@ -291,11 +335,13 @@
         ad1: The android device
         ad2: The android device which is a group owner
     """
-    ad2.droid.wifiP2pStopPeerDiscovery()
-    time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
-    ad1.droid.wifiP2pDiscoverPeers()
     p2p_find_result = False
+    ad1.ed.clear_events(p2pconsts.PEER_AVAILABLE_EVENT)
     while not p2p_find_result:
+        ad2.droid.wifiP2pStopPeerDiscovery()
+        ad1.droid.wifiP2pStopPeerDiscovery()
+        ad2.droid.wifiP2pDiscoverPeers()
+        ad1.droid.wifiP2pDiscoverPeers()
         ad1_event = ad1.ed.pop_event(p2pconsts.PEER_AVAILABLE_EVENT,
                                      p2pconsts.P2P_FIND_TIMEOUT)
         ad1.log.debug(ad1_event['data'])
diff --git a/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
index 66711f3..bb58b02 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
@@ -104,4 +104,4 @@
                 wutils.get_cnss_diag_log(ad)
         for proc in self.tcpdump_proc:
             nutils.stop_tcpdump(proc[0], proc[1], self.test_name)
-        self.tcpdump_proc = []
\ No newline at end of file
+        self.tcpdump_proc = []
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py
index 3a49905..6247643 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py
@@ -27,6 +27,8 @@
 WIFI_NETWORK_CB_ON_UNAVAILABLE = "WifiManagerNetworkCallbackOnUnavailable"
 WIFI_NETWORK_CB_ON_LOST = "WifiManagerNetworkCallbackOnLost"
 WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "WifiNetworkSuggestionPostConnection"
+WIFI_SUBSYSTEM_RESTARTING = "WifiSubsystemRestarting"
+WIFI_SUBSYSTEM_RESTARTED = "WifiSubsystemRestarted"
 
 # These constants will be used by the ACTS wifi tests.
 CONNECT_BY_CONFIG_SUCCESS = 'WifiManagerConnectByConfigOnSuccess'
@@ -44,15 +46,23 @@
 WIFI_AP_ENABLED_STATE = 13
 WIFI_AP_FAILED_STATE = 14
 
+SOFTAP_RANDOMIZATION_NONE = 0
+SOFTAP_RANDOMIZATION_PERSISTENT = 1
+
 # Callback Event for client number change:
 # WifiManagerSoftApCallback-[callbackId]-OnNumClientsChanged
+SOFTAP_NUMBER_CLIENTS_CHANGED_WITH_INFO = "-OnConnectedClientsChangedWithInfo"
 SOFTAP_NUMBER_CLIENTS_CHANGED = "-OnNumClientsChanged"
 SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY = "NumClients"
 SOFTAP_CLIENTS_MACS_CALLBACK_KEY = "MacAddresses"
 # Callback Event for softap info change
 SOFTAP_INFO_CHANGED = "-OnInfoChanged"
+SOFTAP_INFOLIST_CHANGED = "-OnInfoListChanged"
 SOFTAP_INFO_FREQUENCY_CALLBACK_KEY = "frequency"
 SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY = "bandwidth"
+SOFTAP_INFO_WIFISTANDARD_CALLBACK_KEY = "wifiStandard"
+SOFTAP_INFO_AUTO_SHUTDOWN_CALLBACK_KEY = "autoShutdownTimeoutMillis"
+SOFTAP_INFO_BSSID_CALLBACK_KEY = "bssid"
 # Callback Event for softap client blocking
 SOFTAP_BLOCKING_CLIENT_CONNECTING = "-OnBlockedClientConnecting"
 SOFTAP_BLOCKING_CLIENT_REASON_KEY = "BlockedReason"
@@ -63,9 +73,18 @@
 # Callback Event for softap capability
 SOFTAP_CAPABILITY_CHANGED = "-OnCapabilityChanged"
 SOFTAP_CAPABILITY_MAX_SUPPORTED_CLIENTS = "maxSupportedClients"
+SOFTAP_CAPABILITY_24GHZ_SUPPORTED_CHANNEL_LIST = "supported2GHzChannellist"
+SOFTAP_CAPABILITY_5GHZ_SUPPORTED_CHANNEL_LIST = "supported5GHzChannellist"
+SOFTAP_CAPABILITY_6GHZ_SUPPORTED_CHANNEL_LIST = "supported6GHzChannellist"
+SOFTAP_CAPABILITY_60GHZ_SUPPORTED_CHANNEL_LIST = "supported60GHzChannellist"
 SOFTAP_CAPABILITY_FEATURE_ACS = "acsOffloadSupported"
 SOFTAP_CAPABILITY_FEATURE_CLIENT_CONTROL = "clientForceDisconnectSupported"
 SOFTAP_CAPABILITY_FEATURE_WPA3_SAE = "wpa3SaeSupported"
+SOFTAP_CAPABILITY_FEATURE_IEEE80211AX = "ieee80211axSupported"
+SOFTAP_CAPABILITY_FEATURE_24GHZ = "24gSupported"
+SOFTAP_CAPABILITY_FEATURE_5GHZ = "5gSupported"
+SOFTAP_CAPABILITY_FEATURE_6GHZ = "6gSupported"
+SOFTAP_CAPABILITY_FEATURE_60GHZ = "60gSupported"
 
 DEFAULT_SOFTAP_TIMEOUT_S = 600 # 10 minutes
 
@@ -81,3 +100,17 @@
 
 # Delay before registering the match callback.
 NETWORK_REQUEST_CB_REGISTER_DELAY_SEC = 2
+
+# Constants for JSONObject representation of CoexUnsafeChannel
+COEX_BAND = "band"
+COEX_BAND_24_GHZ = "24_GHZ"
+COEX_BAND_5_GHZ = "5_GHZ"
+COEX_CHANNEL = "channel"
+COEX_POWER_CAP_DBM = "powerCapDbm"
+
+# Constants for bundle keys for CoexCallback#onCoexUnsafeChannelsChanged
+KEY_COEX_UNSAFE_CHANNELS = "KEY_COEX_UNSAFE_CHANNELS"
+KEY_COEX_RESTRICTIONS = "KEY_COEX_RESTRICTIONS"
+
+# WiFi standards
+WIFI_STANDARD_11AX = 6
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_datastore_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_datastore_utils.py
old mode 100644
new mode 100755
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
index 577acb3..8afe1d5 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
@@ -14,9 +14,10 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import bokeh, bokeh.plotting
+import bokeh, bokeh.plotting, bokeh.io
 import collections
 import hashlib
+import ipaddress
 import itertools
 import json
 import logging
@@ -27,6 +28,7 @@
 import time
 from acts.controllers.android_device import AndroidDevice
 from acts.controllers.utils_lib import ssh
+from acts import asserts
 from acts import utils
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from concurrent.futures import ThreadPoolExecutor
@@ -39,6 +41,7 @@
 SCAN_RESULTS = 'wpa_cli scan_results'
 SIGNAL_POLL = 'wpa_cli signal_poll'
 WPA_CLI_STATUS = 'wpa_cli status'
+DISCONNECTION_MESSAGE_BRCM = 'driver adapter not found'
 CONST_3dB = 3.01029995664
 RSSI_ERROR_VAL = float('nan')
 RTT_REGEX = re.compile(r'^\[(?P<timestamp>\S+)\] .*? time=(?P<rtt>\S+)')
@@ -59,139 +62,6 @@
     return wrap
 
 
-# Link layer stats utilities
-class LinkLayerStats():
-
-    LLSTATS_CMD = 'cat /d/wlan0/ll_stats'
-    PEER_REGEX = 'LL_STATS_PEER_ALL'
-    MCS_REGEX = re.compile(
-        r'preamble: (?P<mode>\S+), nss: (?P<num_streams>\S+), bw: (?P<bw>\S+), '
-        'mcs: (?P<mcs>\S+), bitrate: (?P<rate>\S+), txmpdu: (?P<txmpdu>\S+), '
-        'rxmpdu: (?P<rxmpdu>\S+), mpdu_lost: (?P<mpdu_lost>\S+), '
-        'retries: (?P<retries>\S+), retries_short: (?P<retries_short>\S+), '
-        'retries_long: (?P<retries_long>\S+)')
-    MCS_ID = collections.namedtuple(
-        'mcs_id', ['mode', 'num_streams', 'bandwidth', 'mcs', 'rate'])
-    MODE_MAP = {'0': '11a/g', '1': '11b', '2': '11n', '3': '11ac'}
-    BW_MAP = {'0': 20, '1': 40, '2': 80}
-
-    def __init__(self, dut, llstats_enabled=True):
-        self.dut = dut
-        self.llstats_enabled = llstats_enabled
-        self.llstats_cumulative = self._empty_llstats()
-        self.llstats_incremental = self._empty_llstats()
-
-    def update_stats(self):
-        if self.llstats_enabled:
-            try:
-                llstats_output = self.dut.adb.shell(self.LLSTATS_CMD,
-                                                    timeout=0.1)
-            except:
-                llstats_output = ''
-        else:
-            llstats_output = ''
-        self._update_stats(llstats_output)
-
-    def reset_stats(self):
-        self.llstats_cumulative = self._empty_llstats()
-        self.llstats_incremental = self._empty_llstats()
-
-    def _empty_llstats(self):
-        return collections.OrderedDict(mcs_stats=collections.OrderedDict(),
-                                       summary=collections.OrderedDict())
-
-    def _empty_mcs_stat(self):
-        return collections.OrderedDict(txmpdu=0,
-                                       rxmpdu=0,
-                                       mpdu_lost=0,
-                                       retries=0,
-                                       retries_short=0,
-                                       retries_long=0)
-
-    def _mcs_id_to_string(self, mcs_id):
-        mcs_string = '{} {}MHz Nss{} MCS{} {}Mbps'.format(
-            mcs_id.mode, mcs_id.bandwidth, mcs_id.num_streams, mcs_id.mcs,
-            mcs_id.rate)
-        return mcs_string
-
-    def _parse_mcs_stats(self, llstats_output):
-        llstats_dict = {}
-        # Look for per-peer stats
-        match = re.search(self.PEER_REGEX, llstats_output)
-        if not match:
-            self.reset_stats()
-            return collections.OrderedDict()
-        # Find and process all matches for per stream stats
-        match_iter = re.finditer(self.MCS_REGEX, llstats_output)
-        for match in match_iter:
-            current_mcs = self.MCS_ID(self.MODE_MAP[match.group('mode')],
-                                      int(match.group('num_streams')) + 1,
-                                      self.BW_MAP[match.group('bw')],
-                                      int(match.group('mcs')),
-                                      int(match.group('rate'), 16) / 1000)
-            current_stats = collections.OrderedDict(
-                txmpdu=int(match.group('txmpdu')),
-                rxmpdu=int(match.group('rxmpdu')),
-                mpdu_lost=int(match.group('mpdu_lost')),
-                retries=int(match.group('retries')),
-                retries_short=int(match.group('retries_short')),
-                retries_long=int(match.group('retries_long')))
-            llstats_dict[self._mcs_id_to_string(current_mcs)] = current_stats
-        return llstats_dict
-
-    def _diff_mcs_stats(self, new_stats, old_stats):
-        stats_diff = collections.OrderedDict()
-        for stat_key in new_stats.keys():
-            stats_diff[stat_key] = new_stats[stat_key] - old_stats[stat_key]
-        return stats_diff
-
-    def _generate_stats_summary(self, llstats_dict):
-        llstats_summary = collections.OrderedDict(common_tx_mcs=None,
-                                                  common_tx_mcs_count=0,
-                                                  common_tx_mcs_freq=0,
-                                                  common_rx_mcs=None,
-                                                  common_rx_mcs_count=0,
-                                                  common_rx_mcs_freq=0)
-        txmpdu_count = 0
-        rxmpdu_count = 0
-        for mcs_id, mcs_stats in llstats_dict['mcs_stats'].items():
-            if mcs_stats['txmpdu'] > llstats_summary['common_tx_mcs_count']:
-                llstats_summary['common_tx_mcs'] = mcs_id
-                llstats_summary['common_tx_mcs_count'] = mcs_stats['txmpdu']
-            if mcs_stats['rxmpdu'] > llstats_summary['common_rx_mcs_count']:
-                llstats_summary['common_rx_mcs'] = mcs_id
-                llstats_summary['common_rx_mcs_count'] = mcs_stats['rxmpdu']
-            txmpdu_count += mcs_stats['txmpdu']
-            rxmpdu_count += mcs_stats['rxmpdu']
-        if txmpdu_count:
-            llstats_summary['common_tx_mcs_freq'] = (
-                llstats_summary['common_tx_mcs_count'] / txmpdu_count)
-        if rxmpdu_count:
-            llstats_summary['common_rx_mcs_freq'] = (
-                llstats_summary['common_rx_mcs_count'] / rxmpdu_count)
-        return llstats_summary
-
-    def _update_stats(self, llstats_output):
-        # Parse stats
-        new_llstats = self._empty_llstats()
-        new_llstats['mcs_stats'] = self._parse_mcs_stats(llstats_output)
-        # Save old stats and set new cumulative stats
-        old_llstats = self.llstats_cumulative.copy()
-        self.llstats_cumulative = new_llstats.copy()
-        # Compute difference between new and old stats
-        self.llstats_incremental = self._empty_llstats()
-        for mcs_id, new_mcs_stats in new_llstats['mcs_stats'].items():
-            old_mcs_stats = old_llstats['mcs_stats'].get(
-                mcs_id, self._empty_mcs_stat())
-            self.llstats_incremental['mcs_stats'][
-                mcs_id] = self._diff_mcs_stats(new_mcs_stats, old_mcs_stats)
-        # Generate llstats summary
-        self.llstats_incremental['summary'] = self._generate_stats_summary(
-            self.llstats_incremental)
-        self.llstats_cumulative['summary'] = self._generate_stats_summary(
-            self.llstats_cumulative)
-
-
 # JSON serializer
 def serialize_dict(input_dict):
     """Function to serialize dicts to enable JSON output"""
@@ -213,6 +83,62 @@
         return value
 
 
+# Miscellaneous Wifi Utilities
+def extract_sub_dict(full_dict, fields):
+    sub_dict = collections.OrderedDict(
+        (field, full_dict[field]) for field in fields)
+    return sub_dict
+
+
+def validate_network(dut, ssid):
+    """Check that DUT has a valid internet connection through expected SSID
+
+    Args:
+        dut: android device of interest
+        ssid: expected ssid
+    """
+    current_network = dut.droid.wifiGetConnectionInfo()
+    try:
+        connected = wutils.validate_connection(dut) is not None
+    except:
+        connected = False
+    if connected and current_network['SSID'] == ssid:
+        return True
+    else:
+        return False
+
+
+def get_server_address(ssh_connection, dut_ip, subnet_mask):
+    """Get server address on a specific subnet,
+
+    This function retrieves the LAN or WAN IP of a remote machine used in
+    testing. If subnet_mask is set to 'public' it returns a machines global ip,
+    else it returns the ip belonging to the dut local network given the dut's
+    ip and subnet mask.
+
+    Args:
+        ssh_connection: object representing server for which we want an ip
+        dut_ip: string in ip address format, i.e., xxx.xxx.xxx.xxx
+        subnet_mask: string representing subnet mask (public for global ip)
+    """
+    ifconfig_out = ssh_connection.run('ifconfig').stdout
+    ip_list = re.findall('inet (?:addr:)?(\d+.\d+.\d+.\d+)', ifconfig_out)
+    ip_list = [ipaddress.ip_address(ip) for ip in ip_list]
+
+    if subnet_mask == 'public':
+        for ip in ip_list:
+            # is_global is not used to allow for CGNAT ips in 100.x.y.z range
+            if not ip.is_private:
+                return str(ip)
+    else:
+        dut_network = ipaddress.ip_network('{}/{}'.format(dut_ip, subnet_mask),
+                                           strict=False)
+        for ip in ip_list:
+            if ip in dut_network:
+                return str(ip)
+    logging.error('No IP address found in requested subnet')
+
+
 # Plotting Utilities
 class BokehFigure():
     """Class enabling  simplified Bokeh plotting."""
@@ -504,10 +430,10 @@
             output_file: string specifying output file path
             save_json: flag controlling json outputs
         """
-        bokeh.plotting.output_file(output_file)
-        bokeh.plotting.save(self.plot)
         if save_json:
             self._save_figure_json(output_file)
+        bokeh.io.output_file(output_file)
+        bokeh.io.save(self.plot)
 
     @staticmethod
     def save_figures(figure_array, output_file_path, save_json=True):
@@ -654,19 +580,32 @@
     """
     ping_count = int(ping_duration / ping_interval)
     ping_deadline = int(ping_count * ping_interval) + 1
-    ping_cmd = 'ping -c {} -w {} -i {} -s {} -D'.format(
+    ping_cmd_linux = 'ping -c {} -w {} -i {} -s {} -D'.format(
         ping_count,
         ping_deadline,
         ping_interval,
         ping_size,
     )
+
+    ping_cmd_macos = 'ping -c {} -t {} -i {} -s {}'.format(
+        ping_count,
+        ping_deadline,
+        ping_interval,
+        ping_size,
+    )
+
     if isinstance(src_device, AndroidDevice):
-        ping_cmd = '{} {}'.format(ping_cmd, dest_address)
+        ping_cmd = '{} {}'.format(ping_cmd_linux, dest_address)
         ping_output = src_device.adb.shell(ping_cmd,
                                            timeout=ping_deadline + SHORT_SLEEP,
                                            ignore_status=True)
     elif isinstance(src_device, ssh.connection.SshConnection):
-        ping_cmd = 'sudo {} {}'.format(ping_cmd, dest_address)
+        platform = src_device.run('uname').stdout
+        if 'linux' in platform.lower():
+            ping_cmd = 'sudo {} {}'.format(ping_cmd_linux, dest_address)
+        elif 'darwin' in platform.lower():
+            ping_cmd = "sudo {} {}| while IFS= read -r line; do printf '[%s] %s\n' \"$(gdate '+%s.%N')\" \"$line\"; done".format(
+                ping_cmd_macos, dest_address)
         ping_output = src_device.run(ping_cmd,
                                      timeout=ping_deadline + SHORT_SLEEP,
                                      ignore_status=True).stdout
@@ -719,7 +658,7 @@
     if ipv6:
         iperf_args = iperf_args + '-6 '
     if traffic_type.upper() == 'UDP':
-        iperf_args = iperf_args + '-u -b {} -l 1400 -P {} '.format(
+        iperf_args = iperf_args + '-u -b {} -l 1470 -P {} '.format(
             udp_throughput, num_processes)
     elif traffic_type.upper() == 'TCP':
         iperf_args = iperf_args + '-P {} '.format(num_processes)
@@ -730,201 +669,6 @@
     return iperf_args
 
 
-# Rssi Utilities
-def empty_rssi_result():
-    return collections.OrderedDict([('data', []), ('mean', None),
-                                    ('stdev', None)])
-
-
-def get_connected_rssi(dut,
-                       num_measurements=1,
-                       polling_frequency=SHORT_SLEEP,
-                       first_measurement_delay=0,
-                       disconnect_warning=True,
-                       ignore_samples=0,
-                       interface=None):
-    """Gets all RSSI values reported for the connected access point/BSSID.
-
-    Args:
-        dut: android device object from which to get RSSI
-        num_measurements: number of scans done, and RSSIs collected
-        polling_frequency: time to wait between RSSI measurements
-        disconnect_warning: boolean controlling disconnection logging messages
-        ignore_samples: number of leading samples to ignore
-    Returns:
-        connected_rssi: dict containing the measurements results for
-        all reported RSSI values (signal_poll, per chain, etc.) and their
-        statistics
-    """
-    # yapf: disable
-    connected_rssi = collections.OrderedDict(
-        [('time_stamp', []),
-         ('bssid', []), ('ssid', []), ('frequency', []),
-         ('signal_poll_rssi', empty_rssi_result()),
-         ('signal_poll_avg_rssi', empty_rssi_result()),
-         ('chain_0_rssi', empty_rssi_result()),
-         ('chain_1_rssi', empty_rssi_result())])
-    # yapf: enable
-    previous_bssid = 'disconnected'
-    t0 = time.time()
-    time.sleep(first_measurement_delay)
-    for idx in range(num_measurements):
-        measurement_start_time = time.time()
-        connected_rssi['time_stamp'].append(measurement_start_time - t0)
-        # Get signal poll RSSI
-        if interface is None:
-            status_output = dut.adb.shell(WPA_CLI_STATUS)
-        else:
-            status_output = dut.adb.shell(
-                'wpa_cli -i {} status'.format(interface))
-        match = re.search('bssid=.*', status_output)
-        if match:
-            current_bssid = match.group(0).split('=')[1]
-            connected_rssi['bssid'].append(current_bssid)
-        else:
-            current_bssid = 'disconnected'
-            connected_rssi['bssid'].append(current_bssid)
-            if disconnect_warning and previous_bssid != 'disconnected':
-                logging.warning('WIFI DISCONNECT DETECTED!')
-        previous_bssid = current_bssid
-        match = re.search('\s+ssid=.*', status_output)
-        if match:
-            ssid = match.group(0).split('=')[1]
-            connected_rssi['ssid'].append(ssid)
-        else:
-            connected_rssi['ssid'].append('disconnected')
-        if interface is None:
-            signal_poll_output = dut.adb.shell(SIGNAL_POLL)
-        else:
-            signal_poll_output = dut.adb.shell(
-                'wpa_cli -i {} signal_poll'.format(interface))
-        match = re.search('FREQUENCY=.*', signal_poll_output)
-        if match:
-            frequency = int(match.group(0).split('=')[1])
-            connected_rssi['frequency'].append(frequency)
-        else:
-            connected_rssi['frequency'].append(RSSI_ERROR_VAL)
-        match = re.search('RSSI=.*', signal_poll_output)
-        if match:
-            temp_rssi = int(match.group(0).split('=')[1])
-            if temp_rssi == -9999 or temp_rssi == 0:
-                connected_rssi['signal_poll_rssi']['data'].append(
-                    RSSI_ERROR_VAL)
-            else:
-                connected_rssi['signal_poll_rssi']['data'].append(temp_rssi)
-        else:
-            connected_rssi['signal_poll_rssi']['data'].append(RSSI_ERROR_VAL)
-        match = re.search('AVG_RSSI=.*', signal_poll_output)
-        if match:
-            connected_rssi['signal_poll_avg_rssi']['data'].append(
-                int(match.group(0).split('=')[1]))
-        else:
-            connected_rssi['signal_poll_avg_rssi']['data'].append(
-                RSSI_ERROR_VAL)
-
-        # Get per chain RSSI
-        if interface is None:
-            per_chain_rssi = dut.adb.shell(STATION_DUMP)
-        else:
-            per_chain_rssi = ''
-        match = re.search('.*signal avg:.*', per_chain_rssi)
-        if match:
-            per_chain_rssi = per_chain_rssi[per_chain_rssi.find('[') +
-                                            1:per_chain_rssi.find(']')]
-            per_chain_rssi = per_chain_rssi.split(', ')
-            connected_rssi['chain_0_rssi']['data'].append(
-                int(per_chain_rssi[0]))
-            connected_rssi['chain_1_rssi']['data'].append(
-                int(per_chain_rssi[1]))
-        else:
-            connected_rssi['chain_0_rssi']['data'].append(RSSI_ERROR_VAL)
-            connected_rssi['chain_1_rssi']['data'].append(RSSI_ERROR_VAL)
-        measurement_elapsed_time = time.time() - measurement_start_time
-        time.sleep(max(0, polling_frequency - measurement_elapsed_time))
-
-    # Compute mean RSSIs. Only average valid readings.
-    # Output RSSI_ERROR_VAL if no valid connected readings found.
-    for key, val in connected_rssi.copy().items():
-        if 'data' not in val:
-            continue
-        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
-        if len(filtered_rssi_values) > ignore_samples:
-            filtered_rssi_values = filtered_rssi_values[ignore_samples:]
-        if filtered_rssi_values:
-            connected_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
-            if len(filtered_rssi_values) > 1:
-                connected_rssi[key]['stdev'] = statistics.stdev(
-                    filtered_rssi_values)
-            else:
-                connected_rssi[key]['stdev'] = 0
-        else:
-            connected_rssi[key]['mean'] = RSSI_ERROR_VAL
-            connected_rssi[key]['stdev'] = RSSI_ERROR_VAL
-    return connected_rssi
-
-
-@nonblocking
-def get_connected_rssi_nb(dut,
-                          num_measurements=1,
-                          polling_frequency=SHORT_SLEEP,
-                          first_measurement_delay=0,
-                          disconnect_warning=True,
-                          ignore_samples=0,
-                          interface=None):
-    return get_connected_rssi(dut, num_measurements, polling_frequency,
-                              first_measurement_delay, disconnect_warning,
-                              ignore_samples, interface)
-
-
-def get_scan_rssi(dut, tracked_bssids, num_measurements=1):
-    """Gets scan RSSI for specified BSSIDs.
-
-    Args:
-        dut: android device object from which to get RSSI
-        tracked_bssids: array of BSSIDs to gather RSSI data for
-        num_measurements: number of scans done, and RSSIs collected
-    Returns:
-        scan_rssi: dict containing the measurement results as well as the
-        statistics of the scan RSSI for all BSSIDs in tracked_bssids
-    """
-    scan_rssi = collections.OrderedDict()
-    for bssid in tracked_bssids:
-        scan_rssi[bssid] = empty_rssi_result()
-    for idx in range(num_measurements):
-        scan_output = dut.adb.shell(SCAN)
-        time.sleep(MED_SLEEP)
-        scan_output = dut.adb.shell(SCAN_RESULTS)
-        for bssid in tracked_bssids:
-            bssid_result = re.search(bssid + '.*',
-                                     scan_output,
-                                     flags=re.IGNORECASE)
-            if bssid_result:
-                bssid_result = bssid_result.group(0).split('\t')
-                scan_rssi[bssid]['data'].append(int(bssid_result[2]))
-            else:
-                scan_rssi[bssid]['data'].append(RSSI_ERROR_VAL)
-    # Compute mean RSSIs. Only average valid readings.
-    # Output RSSI_ERROR_VAL if no readings found.
-    for key, val in scan_rssi.items():
-        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
-        if filtered_rssi_values:
-            scan_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
-            if len(filtered_rssi_values) > 1:
-                scan_rssi[key]['stdev'] = statistics.stdev(
-                    filtered_rssi_values)
-            else:
-                scan_rssi[key]['stdev'] = 0
-        else:
-            scan_rssi[key]['mean'] = RSSI_ERROR_VAL
-            scan_rssi[key]['stdev'] = RSSI_ERROR_VAL
-    return scan_rssi
-
-
-@nonblocking
-def get_scan_rssi_nb(dut, tracked_bssids, num_measurements=1):
-    return get_scan_rssi(dut, tracked_bssids, num_measurements)
-
-
 # Attenuator Utilities
 def atten_by_label(atten_list, path_label, atten_level):
     """Attenuate signals according to their path label.
@@ -975,7 +719,7 @@
     current_rssi = current_rssi['signal_poll_rssi']['mean']
     ping_future.result()
     target_atten = 0
-    logging.debug("RSSI @ {0:.2f}dB attenuation = {1:.2f}".format(
+    logging.debug('RSSI @ {0:.2f}dB attenuation = {1:.2f}'.format(
         target_atten, current_rssi))
     within_range = 0
     for idx in range(20):
@@ -1000,7 +744,7 @@
                                           ignore_samples=1)
         current_rssi = current_rssi['signal_poll_rssi']['mean']
         ping_future.result()
-        logging.info("RSSI @ {0:.2f}dB attenuation = {1:.2f}".format(
+        logging.info('RSSI @ {0:.2f}dB attenuation = {1:.2f}'.format(
             target_atten, current_rssi))
         if abs(current_rssi - target_rssi) < 1:
             if within_range:
@@ -1016,7 +760,10 @@
     return target_atten
 
 
-def get_current_atten_dut_chain_map(attenuators, dut, ping_server):
+def get_current_atten_dut_chain_map(attenuators,
+                                    dut,
+                                    ping_server,
+                                    ping_from_dut=False):
     """Function to detect mapping between attenuator ports and DUT chains.
 
     This function detects the mapping between attenuator ports and DUT chains
@@ -1030,6 +777,7 @@
         attenuators: list of attenuator ports
         dut: android device object assumed connected to a wifi network.
         ping_server: ssh connection object to ping server
+        ping_from_dut: boolean controlling whether to ping from or to dut
     Returns:
         chain_map: list of dut chains, one entry per attenuator port
     """
@@ -1038,7 +786,11 @@
         atten.set_atten(0, strict=False, retry=True)
     # Start ping traffic
     dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
-    ping_future = get_ping_stats_nb(ping_server, dut_ip, 11, 0.02, 64)
+    if ping_from_dut:
+        ping_future = get_ping_stats_nb(dut, ping_server._settings.hostname,
+                                        11, 0.02, 64)
+    else:
+        ping_future = get_ping_stats_nb(ping_server, dut_ip, 11, 0.02, 64)
     # Measure starting RSSI
     base_rssi = get_connected_rssi(dut, 4, 0.25, 1)
     chain0_base_rssi = base_rssi['chain_0_rssi']['mean']
@@ -1069,7 +821,11 @@
     return chain_map
 
 
-def get_full_rf_connection_map(attenuators, dut, ping_server, networks):
+def get_full_rf_connection_map(attenuators,
+                               dut,
+                               ping_server,
+                               networks,
+                               ping_from_dut=False):
     """Function to detect per-network connections between attenuator and DUT.
 
     This function detects the mapping between attenuator ports and DUT chains
@@ -1101,73 +857,20 @@
                             assert_on_fail=False,
                             check_connectivity=False)
         rf_map_by_network[net_id] = get_current_atten_dut_chain_map(
-            attenuators, dut, ping_server)
+            attenuators, dut, ping_server, ping_from_dut)
         for idx, chain in enumerate(rf_map_by_network[net_id]):
             if chain:
                 rf_map_by_atten[idx].append({
-                    "network": net_id,
-                    "dut_chain": chain
+                    'network': net_id,
+                    'dut_chain': chain
                 })
-    logging.debug("RF Map (by Network): {}".format(rf_map_by_network))
-    logging.debug("RF Map (by Atten): {}".format(rf_map_by_atten))
+    logging.debug('RF Map (by Network): {}'.format(rf_map_by_network))
+    logging.debug('RF Map (by Atten): {}'.format(rf_map_by_atten))
 
     return rf_map_by_network, rf_map_by_atten
 
 
-# Miscellaneous Wifi Utilities
-def extract_sub_dict(full_dict, fields):
-    sub_dict = collections.OrderedDict(
-        (field, full_dict[field]) for field in fields)
-    return sub_dict
-
-
-def validate_network(dut, ssid):
-    """Check that DUT has a valid internet connection through expected SSID
-
-    Args:
-        dut: android device of interest
-        ssid: expected ssid
-    """
-    current_network = dut.droid.wifiGetConnectionInfo()
-    try:
-        connected = wutils.validate_connection(dut) is not None
-    except:
-        connected = False
-    if connected and current_network['SSID'] == ssid:
-        return True
-    else:
-        return False
-
-
-def get_server_address(ssh_connection, dut_ip, subnet_mask):
-    """Get server address on a specific subnet,
-
-    This function retrieves the LAN IP of a remote machine used in testing,
-    i.e., it returns the server's IP belonging to the same LAN as the DUT.
-
-    Args:
-        ssh_connection: object representing server for which we want an ip
-        dut_ip: string in ip address format, i.e., xxx.xxx.xxx.xxx, specifying
-        the DUT LAN IP we wish to connect to
-        subnet_mask: string representing subnet mask
-    """
-    subnet_mask = subnet_mask.split('.')
-    dut_subnet = [
-        int(dut) & int(subnet)
-        for dut, subnet in zip(dut_ip.split('.'), subnet_mask)
-    ]
-    ifconfig_out = ssh_connection.run('ifconfig').stdout
-    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')
-
-
+# Generic device utils
 def get_dut_temperature(dut):
     """Function to get dut temperature.
 
@@ -1180,15 +883,17 @@
         temperature: device temperature. 0 if temperature could not be read
     """
     candidate_zones = [
-        'skin-therm', 'sdm-therm-monitor', 'sdm-therm-adc', 'back_therm'
+        '/sys/devices/virtual/thermal/tz-by-name/skin-therm/temp',
+        '/sys/devices/virtual/thermal/tz-by-name/sdm-therm-monitor/temp',
+        '/sys/devices/virtual/thermal/tz-by-name/sdm-therm-adc/temp',
+        '/sys/devices/virtual/thermal/tz-by-name/back_therm/temp',
+        '/dev/thermal/tz-by-name/quiet_therm/temp'
     ]
     for zone in candidate_zones:
         try:
-            temperature = int(
-                dut.adb.shell(
-                    'cat /sys/class/thermal/tz-by-name/{}/temp'.format(zone)))
+            temperature = int(dut.adb.shell('cat {}'.format(zone)))
             break
-        except ValueError:
+        except:
             temperature = 0
     if temperature == 0:
         logging.debug('Could not check DUT temperature.')
@@ -1212,7 +917,7 @@
             break
         time.sleep(SHORT_SLEEP)
     elapsed_time = time.time() - start_time
-    logging.debug("DUT Final Temperature: {}C. Cooldown duration: {}".format(
+    logging.debug('DUT Final Temperature: {}C. Cooldown duration: {}'.format(
         temperature, elapsed_time))
 
 
@@ -1233,25 +938,404 @@
     health_check = True
     battery_level = utils.get_battery_level(dut)
     if battery_level < batt_thresh:
-        logging.warning("Battery level low ({}%)".format(battery_level))
+        logging.warning('Battery level low ({}%)'.format(battery_level))
         health_check = False
     else:
-        logging.debug("Battery level = {}%".format(battery_level))
+        logging.debug('Battery level = {}%'.format(battery_level))
 
     temperature = get_dut_temperature(dut)
     if temperature > temp_threshold:
         if cooldown:
             logging.warning(
-                "Waiting for DUT to cooldown. ({} C)".format(temperature))
+                'Waiting for DUT to cooldown. ({} C)'.format(temperature))
             wait_for_dut_cooldown(dut, target_temp=temp_threshold - 5)
         else:
-            logging.warning("DUT Overheating ({} C)".format(temperature))
+            logging.warning('DUT Overheating ({} C)'.format(temperature))
             health_check = False
     else:
-        logging.debug("DUT Temperature = {} C".format(temperature))
+        logging.debug('DUT Temperature = {} C'.format(temperature))
     return health_check
 
 
+# Wifi Device utils
+def detect_wifi_platform(dut):
+    ini_check = len(dut.get_file_names('/vendor/firmware/wlan/qca_cld/'))
+    if ini_check:
+        wifi_platform = 'qcom'
+    else:
+        wifi_platform = 'brcm'
+    return wifi_platform
+
+
+def detect_wifi_decorator(f):
+    def wrap(*args, **kwargs):
+        if 'dut' in kwargs:
+            dut = kwargs['dut']
+        else:
+            dut = next(arg for arg in args if type(arg) == AndroidDevice)
+        f_decorated = '{}_{}'.format(f.__name__, detect_wifi_platform(dut))
+        f_decorated = globals()[f_decorated]
+        return (f_decorated(*args, **kwargs))
+
+    return wrap
+
+
+# Rssi Utilities
+def empty_rssi_result():
+    return collections.OrderedDict([('data', []), ('mean', None),
+                                    ('stdev', None)])
+
+
+@detect_wifi_decorator
+def get_connected_rssi(dut,
+                       num_measurements=1,
+                       polling_frequency=SHORT_SLEEP,
+                       first_measurement_delay=0,
+                       disconnect_warning=True,
+                       ignore_samples=0,
+                       interface=None):
+    """Gets all RSSI values reported for the connected access point/BSSID.
+
+    Args:
+        dut: android device object from which to get RSSI
+        num_measurements: number of scans done, and RSSIs collected
+        polling_frequency: time to wait between RSSI measurements
+        disconnect_warning: boolean controlling disconnection logging messages
+        ignore_samples: number of leading samples to ignore
+    Returns:
+        connected_rssi: dict containing the measurements results for
+        all reported RSSI values (signal_poll, per chain, etc.) and their
+        statistics
+    """
+    pass
+
+
+@nonblocking
+def get_connected_rssi_nb(dut,
+                          num_measurements=1,
+                          polling_frequency=SHORT_SLEEP,
+                          first_measurement_delay=0,
+                          disconnect_warning=True,
+                          ignore_samples=0,
+                          interface=None):
+    return get_connected_rssi(dut, num_measurements, polling_frequency,
+                              first_measurement_delay, disconnect_warning,
+                              ignore_samples, interface)
+
+
+def get_connected_rssi_qcom(dut,
+                            num_measurements=1,
+                            polling_frequency=SHORT_SLEEP,
+                            first_measurement_delay=0,
+                            disconnect_warning=True,
+                            ignore_samples=0,
+                            interface=None):
+    # yapf: disable
+    connected_rssi = collections.OrderedDict(
+        [('time_stamp', []),
+         ('bssid', []), ('ssid', []), ('frequency', []),
+         ('signal_poll_rssi', empty_rssi_result()),
+         ('signal_poll_avg_rssi', empty_rssi_result()),
+         ('chain_0_rssi', empty_rssi_result()),
+         ('chain_1_rssi', empty_rssi_result())])
+    # yapf: enable
+    previous_bssid = 'disconnected'
+    t0 = time.time()
+    time.sleep(first_measurement_delay)
+    for idx in range(num_measurements):
+        measurement_start_time = time.time()
+        connected_rssi['time_stamp'].append(measurement_start_time - t0)
+        # Get signal poll RSSI
+        try:
+            if interface is None:
+                status_output = dut.adb.shell(WPA_CLI_STATUS)
+            else:
+                status_output = dut.adb.shell(
+                    'wpa_cli -i {} status'.format(interface))
+        except:
+            status_output = ''
+        match = re.search('bssid=.*', status_output)
+        if match:
+            current_bssid = match.group(0).split('=')[1]
+            connected_rssi['bssid'].append(current_bssid)
+        else:
+            current_bssid = 'disconnected'
+            connected_rssi['bssid'].append(current_bssid)
+            if disconnect_warning and previous_bssid != 'disconnected':
+                logging.warning('WIFI DISCONNECT DETECTED!')
+        previous_bssid = current_bssid
+        match = re.search('\s+ssid=.*', status_output)
+        if match:
+            ssid = match.group(0).split('=')[1]
+            connected_rssi['ssid'].append(ssid)
+        else:
+            connected_rssi['ssid'].append('disconnected')
+        try:
+            if interface is None:
+                signal_poll_output = dut.adb.shell(SIGNAL_POLL)
+            else:
+                signal_poll_output = dut.adb.shell(
+                    'wpa_cli -i {} signal_poll'.format(interface))
+        except:
+            signal_poll_output = ''
+        match = re.search('FREQUENCY=.*', signal_poll_output)
+        if match:
+            frequency = int(match.group(0).split('=')[1])
+            connected_rssi['frequency'].append(frequency)
+        else:
+            connected_rssi['frequency'].append(RSSI_ERROR_VAL)
+        match = re.search('RSSI=.*', signal_poll_output)
+        if match:
+            temp_rssi = int(match.group(0).split('=')[1])
+            if temp_rssi == -9999 or temp_rssi == 0:
+                connected_rssi['signal_poll_rssi']['data'].append(
+                    RSSI_ERROR_VAL)
+            else:
+                connected_rssi['signal_poll_rssi']['data'].append(temp_rssi)
+        else:
+            connected_rssi['signal_poll_rssi']['data'].append(RSSI_ERROR_VAL)
+        match = re.search('AVG_RSSI=.*', signal_poll_output)
+        if match:
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                int(match.group(0).split('=')[1]))
+        else:
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                RSSI_ERROR_VAL)
+
+        # Get per chain RSSI
+        try:
+            if interface is None:
+                per_chain_rssi = dut.adb.shell(STATION_DUMP)
+            else:
+                per_chain_rssi = ''
+        except:
+            per_chain_rssi = ''
+        match = re.search('.*signal avg:.*', per_chain_rssi)
+        if match:
+            per_chain_rssi = per_chain_rssi[per_chain_rssi.find('[') +
+                                            1:per_chain_rssi.find(']')]
+            per_chain_rssi = per_chain_rssi.split(', ')
+            connected_rssi['chain_0_rssi']['data'].append(
+                int(per_chain_rssi[0]))
+            connected_rssi['chain_1_rssi']['data'].append(
+                int(per_chain_rssi[1]))
+        else:
+            connected_rssi['chain_0_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['chain_1_rssi']['data'].append(RSSI_ERROR_VAL)
+        measurement_elapsed_time = time.time() - measurement_start_time
+        time.sleep(max(0, polling_frequency - measurement_elapsed_time))
+
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no valid connected readings found.
+    for key, val in connected_rssi.copy().items():
+        if 'data' not in val:
+            continue
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if len(filtered_rssi_values) > ignore_samples:
+            filtered_rssi_values = filtered_rssi_values[ignore_samples:]
+        if filtered_rssi_values:
+            connected_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                connected_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                connected_rssi[key]['stdev'] = 0
+        else:
+            connected_rssi[key]['mean'] = RSSI_ERROR_VAL
+            connected_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return connected_rssi
+
+
+def get_connected_rssi_brcm(dut,
+                            num_measurements=1,
+                            polling_frequency=SHORT_SLEEP,
+                            first_measurement_delay=0,
+                            disconnect_warning=True,
+                            ignore_samples=0,
+                            interface=None):
+    # yapf: disable
+    connected_rssi = collections.OrderedDict(
+        [('time_stamp', []),
+         ('bssid', []), ('ssid', []), ('frequency', []),
+         ('signal_poll_rssi', empty_rssi_result()),
+         ('signal_poll_avg_rssi', empty_rssi_result()),
+         ('chain_0_rssi', empty_rssi_result()),
+         ('chain_1_rssi', empty_rssi_result())])
+
+    # yapf: enable
+    previous_bssid = 'disconnected'
+    t0 = time.time()
+    time.sleep(first_measurement_delay)
+    for idx in range(num_measurements):
+        measurement_start_time = time.time()
+        connected_rssi['time_stamp'].append(measurement_start_time - t0)
+        # Get signal poll RSSI
+        status_output = dut.adb.shell('wl assoc')
+        match = re.search('BSSID:.*', status_output)
+
+        if match:
+            current_bssid = match.group(0).split('\t')[0]
+            current_bssid = current_bssid.split(' ')[1]
+            connected_rssi['bssid'].append(current_bssid)
+
+        else:
+            current_bssid = 'disconnected'
+            connected_rssi['bssid'].append(current_bssid)
+            if disconnect_warning and previous_bssid != 'disconnected':
+                logging.warning('WIFI DISCONNECT DETECTED!')
+
+        previous_bssid = current_bssid
+        match = re.search('SSID:.*', status_output)
+        if match:
+            ssid = match.group(0).split(': ')[1]
+            connected_rssi['ssid'].append(ssid)
+        else:
+            connected_rssi['ssid'].append('disconnected')
+
+        #TODO: SEARCH MAP ; PICK CENTER CHANNEL
+        match = re.search('Primary channel:.*', status_output)
+        if match:
+            frequency = int(match.group(0).split(':')[1])
+            connected_rssi['frequency'].append(frequency)
+        else:
+            connected_rssi['frequency'].append(RSSI_ERROR_VAL)
+
+        try:
+            per_chain_rssi = dut.adb.shell('wl phy_rssi_ant')
+        except:
+            per_chain_rssi = DISCONNECTION_MESSAGE_BRCM
+        if DISCONNECTION_MESSAGE_BRCM not in per_chain_rssi:
+            per_chain_rssi = per_chain_rssi.split(' ')
+            chain_0_rssi = int(per_chain_rssi[1])
+            chain_1_rssi = int(per_chain_rssi[4])
+            connected_rssi['chain_0_rssi']['data'].append(chain_0_rssi)
+            connected_rssi['chain_1_rssi']['data'].append(chain_1_rssi)
+            combined_rssi = math.pow(10, chain_0_rssi / 10) + math.pow(
+                10, chain_1_rssi / 10)
+            combined_rssi = 10 * math.log10(combined_rssi)
+            connected_rssi['signal_poll_rssi']['data'].append(combined_rssi)
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                combined_rssi)
+        else:
+            connected_rssi['chain_0_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['chain_1_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['signal_poll_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                RSSI_ERROR_VAL)
+        measurement_elapsed_time = time.time() - measurement_start_time
+        time.sleep(max(0, polling_frequency - measurement_elapsed_time))
+
+    # Statistics, Statistics
+    for key, val in connected_rssi.copy().items():
+        if 'data' not in val:
+            continue
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if len(filtered_rssi_values) > ignore_samples:
+            filtered_rssi_values = filtered_rssi_values[ignore_samples:]
+        if filtered_rssi_values:
+            connected_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                connected_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                connected_rssi[key]['stdev'] = 0
+        else:
+            connected_rssi[key]['mean'] = RSSI_ERROR_VAL
+            connected_rssi[key]['stdev'] = RSSI_ERROR_VAL
+
+    return connected_rssi
+
+
+@detect_wifi_decorator
+def get_scan_rssi(dut, tracked_bssids, num_measurements=1):
+    """Gets scan RSSI for specified BSSIDs.
+
+    Args:
+        dut: android device object from which to get RSSI
+        tracked_bssids: array of BSSIDs to gather RSSI data for
+        num_measurements: number of scans done, and RSSIs collected
+    Returns:
+        scan_rssi: dict containing the measurement results as well as the
+        statistics of the scan RSSI for all BSSIDs in tracked_bssids
+    """
+    pass
+
+
+@nonblocking
+def get_scan_rssi_nb(dut, tracked_bssids, num_measurements=1):
+    return get_scan_rssi(dut, tracked_bssids, num_measurements)
+
+
+def get_scan_rssi_qcom(dut, tracked_bssids, num_measurements=1):
+    scan_rssi = collections.OrderedDict()
+    for bssid in tracked_bssids:
+        scan_rssi[bssid] = empty_rssi_result()
+    for idx in range(num_measurements):
+        scan_output = dut.adb.shell(SCAN)
+        time.sleep(MED_SLEEP)
+        scan_output = dut.adb.shell(SCAN_RESULTS)
+        for bssid in tracked_bssids:
+            bssid_result = re.search(bssid + '.*',
+                                     scan_output,
+                                     flags=re.IGNORECASE)
+            if bssid_result:
+                bssid_result = bssid_result.group(0).split('\t')
+                scan_rssi[bssid]['data'].append(int(bssid_result[2]))
+            else:
+                scan_rssi[bssid]['data'].append(RSSI_ERROR_VAL)
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no readings found.
+    for key, val in scan_rssi.items():
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if filtered_rssi_values:
+            scan_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                scan_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                scan_rssi[key]['stdev'] = 0
+        else:
+            scan_rssi[key]['mean'] = RSSI_ERROR_VAL
+            scan_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return scan_rssi
+
+
+def get_scan_rssi_brcm(dut, tracked_bssids, num_measurements=1):
+    scan_rssi = collections.OrderedDict()
+    for bssid in tracked_bssids:
+        scan_rssi[bssid] = empty_rssi_result()
+    for idx in range(num_measurements):
+        scan_output = dut.adb.shell('cmd wifi start-scan')
+        time.sleep(MED_SLEEP)
+        scan_output = dut.adb.shell('cmd wifi list-scan-results')
+        for bssid in tracked_bssids:
+            bssid_result = re.search(bssid + '.*',
+                                     scan_output,
+                                     flags=re.IGNORECASE)
+            if bssid_result:
+                bssid_result = bssid_result.group(0).split()
+                print(bssid_result)
+                scan_rssi[bssid]['data'].append(int(bssid_result[2]))
+            else:
+                scan_rssi[bssid]['data'].append(RSSI_ERROR_VAL)
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no readings found.
+    for key, val in scan_rssi.items():
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if filtered_rssi_values:
+            scan_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                scan_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                scan_rssi[key]['stdev'] = 0
+        else:
+            scan_rssi[key]['mean'] = RSSI_ERROR_VAL
+            scan_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return scan_rssi
+
+
+@detect_wifi_decorator
 def get_sw_signature(dut):
     """Function that checks the signature for wifi firmware and config files.
 
@@ -1259,6 +1343,10 @@
         bdf_signature: signature consisting of last three digits of bdf cksums
         fw_signature: floating point firmware version, i.e., major.minor
     """
+    pass
+
+
+def get_sw_signature_qcom(dut):
     bdf_output = dut.adb.shell('cksum /vendor/firmware/bdwlan*')
     logging.debug('BDF Checksum output: {}'.format(bdf_output))
     bdf_signature = sum(
@@ -1271,13 +1359,34 @@
     fw_signature = float('.'.join(fw_signature))
     serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
     return {
-        'bdf_signature': bdf_signature,
+        'config_signature': bdf_signature,
         'fw_signature': fw_signature,
         'serial_hash': serial_hash
     }
 
 
-def push_bdf(dut, bdf_file):
+def get_sw_signature_brcm(dut):
+    bdf_output = dut.adb.shell('cksum /vendor/etc/wifi/bcmdhd*')
+    logging.debug('BDF Checksum output: {}'.format(bdf_output))
+    bdf_signature = sum(
+        [int(line.split(' ')[0]) for line in bdf_output.splitlines()]) % 1000
+
+    fw_output = dut.adb.shell('getprop vendor.wlan.firmware.version')
+    logging.debug('Firmware version output: {}'.format(fw_output))
+    fw_version = fw_output.split('.')[-1]
+    driver_output = dut.adb.shell('getprop vendor.wlan.driver.version')
+    driver_version = driver_output.split('.')[-1]
+    fw_signature = float('{}.{}'.format(fw_version, driver_version))
+    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
+    return {
+        'config_signature': bdf_signature,
+        'fw_signature': fw_signature,
+        'serial_hash': serial_hash
+    }
+
+
+@detect_wifi_decorator
+def push_config(dut, config_file):
     """Function to push Wifi BDF files
 
     This function checks for existing wifi bdf files and over writes them all,
@@ -1286,27 +1395,81 @@
 
     Args:
         dut: dut to push bdf file to
-        bdf_file: path to bdf_file to push
+        config_file: path to bdf_file to push
     """
-    bdf_files_list = dut.adb.shell('ls /vendor/firmware/bdwlan*').splitlines()
-    for dst_file in bdf_files_list:
-        dut.push_system_file(bdf_file, dst_file)
+    pass
+
+
+def push_config_qcom(dut, config_file):
+    config_files_list = dut.adb.shell(
+        'ls /vendor/firmware/bdwlan*').splitlines()
+    for dst_file in config_files_list:
+        dut.push_system_file(config_file, dst_file)
     dut.reboot()
 
 
-def push_firmware(dut, wlanmdsp_file, datamsc_file):
+def push_config_brcm(dut, config_file):
+    config_files_list = dut.adb.shell('ls /vendor/etc/*.cal').splitlines()
+    for dst_file in config_files_list:
+        dut.push_system_file(config_file, dst_file)
+    dut.reboot()
+
+
+def push_firmware(dut, firmware_files):
     """Function to push Wifi firmware files
 
     Args:
         dut: dut to push bdf file to
-        wlanmdsp_file: path to wlanmdsp.mbn file
+        firmware_files: path to wlanmdsp.mbn file
         datamsc_file: path to Data.msc file
     """
-    dut.push_system_file(wlanmdsp_file, '/vendor/firmware/wlanmdsp.mbn')
-    dut.push_system_file(datamsc_file, '/vendor/firmware/Data.msc')
+    for file in firmware_files:
+        dut.push_system_file(file, '/vendor/firmware/')
     dut.reboot()
 
 
+@detect_wifi_decorator
+def start_wifi_logging(dut):
+    """Function to start collecting wifi-related logs"""
+    pass
+
+
+def start_wifi_logging_qcom(dut):
+    dut.droid.wifiEnableVerboseLogging(1)
+    msg = "Failed to enable WiFi verbose logging."
+    asserts.assert_equal(dut.droid.wifiGetVerboseLoggingLevel(), 1, msg)
+    logging.info('Starting CNSS logs')
+    dut.adb.shell("find /data/vendor/wifi/wlan_logs/ -type f -delete",
+                  ignore_status=True)
+    dut.adb.shell_nb('cnss_diag -f -s')
+
+
+def start_wifi_logging_brcm(dut):
+    pass
+
+
+@detect_wifi_decorator
+def stop_wifi_logging(dut):
+    """Function to start collecting wifi-related logs"""
+    pass
+
+
+def stop_wifi_logging_qcom(dut):
+    logging.info('Stopping CNSS logs')
+    dut.adb.shell('killall cnss_diag')
+    logs = dut.get_file_names("/data/vendor/wifi/wlan_logs/")
+    if logs:
+        dut.log.info("Pulling cnss_diag logs %s", logs)
+        log_path = os.path.join(dut.device_log_path,
+                                "CNSS_DIAG_%s" % dut.serial)
+        os.makedirs(log_path, exist_ok=True)
+        dut.pull_files(logs, log_path)
+
+
+def stop_wifi_logging_brcm(dut):
+    pass
+
+
 def _set_ini_fields(ini_file_path, ini_field_dict):
     template_regex = r'^{}=[0-9,.x-]+'
     with open(ini_file_path, 'r') as f:
@@ -1315,10 +1478,10 @@
             for field_name, field_value in ini_field_dict.items():
                 line_regex = re.compile(template_regex.format(field_name))
                 if re.match(line_regex, line):
-                    ini_lines[idx] = "{}={}".format(field_name, field_value)
+                    ini_lines[idx] = '{}={}'.format(field_name, field_value)
                     print(ini_lines[idx])
     with open(ini_file_path, 'w') as f:
-        f.write("\n".join(ini_lines) + "\n")
+        f.write('\n'.join(ini_lines) + '\n')
 
 
 def _edit_dut_ini(dut, ini_fields):
@@ -1357,16 +1520,16 @@
 
 def set_ini_tx_mode(dut, mode):
     TX_MODE_DICT = {
-        "Auto": 0,
-        "11n": 4,
-        "11ac": 9,
-        "11abg": 1,
-        "11b": 2,
-        "11g": 3,
-        "11g only": 5,
-        "11n only": 6,
-        "11b only": 7,
-        "11ac only": 8
+        'Auto': 0,
+        '11n': 4,
+        '11ac': 9,
+        '11abg': 1,
+        '11b': 2,
+        '11': 3,
+        '11g only': 5,
+        '11n only': 6,
+        '11b only': 7,
+        '11ac only': 8
     }
 
     ini_fields = {
@@ -1377,3 +1540,179 @@
         'gDot11Mode': TX_MODE_DICT[mode]
     }
     _edit_dut_ini(dut, ini_fields)
+
+
+# Link layer stats utilities
+class LinkLayerStats():
+    def __new__(self, dut, llstats_enabled=True):
+        if detect_wifi_platform(dut) == 'qcom':
+            return LinkLayerStatsQcom(dut, llstats_enabled=True)
+        else:
+            return LinkLayerStatsBrcm(dut, llstats_enabled=True)
+
+
+class LinkLayerStatsQcom():
+
+    LLSTATS_CMD = 'cat /d/wlan0/ll_stats'
+    PEER_REGEX = 'LL_STATS_PEER_ALL'
+    MCS_REGEX = re.compile(
+        r'preamble: (?P<mode>\S+), nss: (?P<num_streams>\S+), bw: (?P<bw>\S+), '
+        'mcs: (?P<mcs>\S+), bitrate: (?P<rate>\S+), txmpdu: (?P<txmpdu>\S+), '
+        'rxmpdu: (?P<rxmpdu>\S+), mpdu_lost: (?P<mpdu_lost>\S+), '
+        'retries: (?P<retries>\S+), retries_short: (?P<retries_short>\S+), '
+        'retries_long: (?P<retries_long>\S+)')
+    MCS_ID = collections.namedtuple(
+        'mcs_id', ['mode', 'num_streams', 'bandwidth', 'mcs', 'rate'])
+    MODE_MAP = {'0': '11a/g', '1': '11b', '2': '11n', '3': '11ac'}
+    BW_MAP = {'0': 20, '1': 40, '2': 80}
+
+    def __init__(self, dut, llstats_enabled=True):
+        self.dut = dut
+        self.llstats_enabled = llstats_enabled
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+
+    def update_stats(self):
+        if self.llstats_enabled:
+            try:
+                llstats_output = self.dut.adb.shell(self.LLSTATS_CMD,
+                                                    timeout=0.1)
+            except:
+                llstats_output = ''
+        else:
+            llstats_output = ''
+        self._update_stats(llstats_output)
+
+    def reset_stats(self):
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+
+    def _empty_llstats(self):
+        return collections.OrderedDict(mcs_stats=collections.OrderedDict(),
+                                       summary=collections.OrderedDict())
+
+    def _empty_mcs_stat(self):
+        return collections.OrderedDict(txmpdu=0,
+                                       rxmpdu=0,
+                                       mpdu_lost=0,
+                                       retries=0,
+                                       retries_short=0,
+                                       retries_long=0)
+
+    def _mcs_id_to_string(self, mcs_id):
+        mcs_string = '{} {}MHz Nss{} MCS{} {}Mbps'.format(
+            mcs_id.mode, mcs_id.bandwidth, mcs_id.num_streams, mcs_id.mcs,
+            mcs_id.rate)
+        return mcs_string
+
+    def _parse_mcs_stats(self, llstats_output):
+        llstats_dict = {}
+        # Look for per-peer stats
+        match = re.search(self.PEER_REGEX, llstats_output)
+        if not match:
+            self.reset_stats()
+            return collections.OrderedDict()
+        # Find and process all matches for per stream stats
+        match_iter = re.finditer(self.MCS_REGEX, llstats_output)
+        for match in match_iter:
+            current_mcs = self.MCS_ID(self.MODE_MAP[match.group('mode')],
+                                      int(match.group('num_streams')) + 1,
+                                      self.BW_MAP[match.group('bw')],
+                                      int(match.group('mcs')),
+                                      int(match.group('rate'), 16) / 1000)
+            current_stats = collections.OrderedDict(
+                txmpdu=int(match.group('txmpdu')),
+                rxmpdu=int(match.group('rxmpdu')),
+                mpdu_lost=int(match.group('mpdu_lost')),
+                retries=int(match.group('retries')),
+                retries_short=int(match.group('retries_short')),
+                retries_long=int(match.group('retries_long')))
+            llstats_dict[self._mcs_id_to_string(current_mcs)] = current_stats
+        return llstats_dict
+
+    def _diff_mcs_stats(self, new_stats, old_stats):
+        stats_diff = collections.OrderedDict()
+        for stat_key in new_stats.keys():
+            stats_diff[stat_key] = new_stats[stat_key] - old_stats[stat_key]
+        return stats_diff
+
+    def _generate_stats_summary(self, llstats_dict):
+        llstats_summary = collections.OrderedDict(common_tx_mcs=None,
+                                                  common_tx_mcs_count=0,
+                                                  common_tx_mcs_freq=0,
+                                                  common_rx_mcs=None,
+                                                  common_rx_mcs_count=0,
+                                                  common_rx_mcs_freq=0)
+        txmpdu_count = 0
+        rxmpdu_count = 0
+        for mcs_id, mcs_stats in llstats_dict['mcs_stats'].items():
+            if mcs_stats['txmpdu'] > llstats_summary['common_tx_mcs_count']:
+                llstats_summary['common_tx_mcs'] = mcs_id
+                llstats_summary['common_tx_mcs_count'] = mcs_stats['txmpdu']
+            if mcs_stats['rxmpdu'] > llstats_summary['common_rx_mcs_count']:
+                llstats_summary['common_rx_mcs'] = mcs_id
+                llstats_summary['common_rx_mcs_count'] = mcs_stats['rxmpdu']
+            txmpdu_count += mcs_stats['txmpdu']
+            rxmpdu_count += mcs_stats['rxmpdu']
+        if txmpdu_count:
+            llstats_summary['common_tx_mcs_freq'] = (
+                llstats_summary['common_tx_mcs_count'] / txmpdu_count)
+        if rxmpdu_count:
+            llstats_summary['common_rx_mcs_freq'] = (
+                llstats_summary['common_rx_mcs_count'] / rxmpdu_count)
+        return llstats_summary
+
+    def _update_stats(self, llstats_output):
+        # Parse stats
+        new_llstats = self._empty_llstats()
+        new_llstats['mcs_stats'] = self._parse_mcs_stats(llstats_output)
+        # Save old stats and set new cumulative stats
+        old_llstats = self.llstats_cumulative.copy()
+        self.llstats_cumulative = new_llstats.copy()
+        # Compute difference between new and old stats
+        self.llstats_incremental = self._empty_llstats()
+        for mcs_id, new_mcs_stats in new_llstats['mcs_stats'].items():
+            old_mcs_stats = old_llstats['mcs_stats'].get(
+                mcs_id, self._empty_mcs_stat())
+            self.llstats_incremental['mcs_stats'][
+                mcs_id] = self._diff_mcs_stats(new_mcs_stats, old_mcs_stats)
+        # Generate llstats summary
+        self.llstats_incremental['summary'] = self._generate_stats_summary(
+            self.llstats_incremental)
+        self.llstats_cumulative['summary'] = self._generate_stats_summary(
+            self.llstats_cumulative)
+
+
+class LinkLayerStatsBrcm():
+    def __init__(self, dut, llstats_enabled=True):
+        self.dut = dut
+        self.llstats_enabled = llstats_enabled
+        self.llstats_incremental = self._empty_llstats()
+        self.llstats_cumulative = self.llstats_incremental
+
+    def _empty_llstats(self):
+        return collections.OrderedDict(mcs_stats=collections.OrderedDict(),
+                                       summary=collections.OrderedDict())
+
+    def update_stats(self):
+        self.llstats_incremental = self._empty_llstats()
+        self.llstats_incremental['summary'] = collections.OrderedDict(
+            common_tx_mcs=None,
+            common_tx_mcs_count=1,
+            common_tx_mcs_freq=1,
+            common_rx_mcs=None,
+            common_rx_mcs_count=1,
+            common_rx_mcs_freq=1)
+        if self.llstats_enabled:
+            try:
+                rate_info = self.dut.adb.shell('wl rate_info', timeout=0.1)
+                self.llstats_incremental['summary'][
+                    'common_tx_mcs'] = '{} Mbps'.format(
+                        re.findall('\[Tx\]:'
+                                   ' (\d+[.]*\d* Mbps)', rate_info))
+                self.llstats_incremental['summary'][
+                    'common_rx_mcs'] = '{} Mbps'.format(
+                        re.findall('\[Rx\]:'
+                                   ' (\d+[.]*\d* Mbps)', rate_info))
+            except:
+                pass
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py
deleted file mode 100644
index adbceed..0000000
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py
+++ /dev/null
@@ -1,1433 +0,0 @@
-#!/usr/bin/env python3
-#
-#   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 fcntl
-import os
-import selenium
-import splinter
-import time
-from acts import logger
-from acts.controllers import access_point
-from acts.controllers.ap_lib import bridge_interface
-from acts.controllers.ap_lib import hostapd_security
-from acts.controllers.ap_lib import hostapd_ap_preset
-
-BROWSER_WAIT_SHORT = 1
-BROWSER_WAIT_MED = 3
-BROWSER_WAIT_LONG = 30
-BROWSER_WAIT_EXTRA_LONG = 60
-
-
-def create(configs):
-    """Factory method for retail AP class.
-
-    Args:
-        configs: list of dicts containing ap settings. ap settings must contain
-        the following: brand, model, ip_address, username and password
-    """
-    SUPPORTED_APS = {
-        ("Netgear", "R7000"): "NetgearR7000AP",
-        ("Netgear", "R7000NA"): "NetgearR7000NAAP",
-        ("Netgear", "R7500"): "NetgearR7500AP",
-        ("Netgear", "R7800"): "NetgearR7800AP",
-        ("Netgear", "R8000"): "NetgearR8000AP",
-        ("Netgear", "R8500"): "NetgearR8500AP",
-        ("Netgear", "RAX"): "NetgearRAXAP",
-        ("Google", "Wifi"): "GoogleWifiAP"
-    }
-    objs = []
-    for config in configs:
-        try:
-            ap_class_name = SUPPORTED_APS[(config["brand"], config["model"])]
-            ap_class = globals()[ap_class_name]
-        except KeyError:
-            raise KeyError("Invalid retail AP brand and model combination.")
-        objs.append(ap_class(config))
-    return objs
-
-
-def detroy(objs):
-    for obj in objs:
-        obj.teardown()
-
-
-class BlockingBrowser(splinter.driver.webdriver.chrome.WebDriver):
-    """Class that implements a blocking browser session on top of selenium.
-
-    The class inherits from and builds upon splinter/selenium's webdriver class
-    and makes sure that only one such webdriver is active on a machine at any
-    single time. The class ensures single session operation using a lock file.
-    The class is to be used within context managers (e.g. with statements) to
-    ensure locks are always properly released.
-    """
-    def __init__(self, headless, timeout):
-        """Constructor for BlockingBrowser class.
-
-        Args:
-            headless: boolean to control visible/headless browser operation
-            timeout: maximum time allowed to launch browser
-        """
-        self.log = logger.create_tagged_trace_logger("ChromeDriver")
-        self.chrome_options = splinter.driver.webdriver.chrome.Options()
-        self.chrome_options.add_argument("--no-proxy-server")
-        self.chrome_options.add_argument("--no-sandbox")
-        self.chrome_options.add_argument("--allow-running-insecure-content")
-        self.chrome_options.add_argument("--ignore-certificate-errors")
-        self.chrome_capabilities = selenium.webdriver.common.desired_capabilities.DesiredCapabilities.CHROME.copy(
-        )
-        self.chrome_capabilities["acceptSslCerts"] = True
-        self.chrome_capabilities["acceptInsecureCerts"] = True
-        if headless:
-            self.chrome_options.add_argument("--headless")
-            self.chrome_options.add_argument("--disable-gpu")
-        self.lock_file_path = "/usr/local/bin/chromedriver"
-        self.timeout = timeout
-
-    def __enter__(self):
-        """Entry context manager for BlockingBrowser.
-
-        The enter context manager for BlockingBrowser attempts to lock the
-        browser file. If successful, it launches and returns a chromedriver
-        session. If an exception occurs while starting the browser, the lock
-        file is released.
-        """
-        self.lock_file = open(self.lock_file_path, "r")
-        start_time = time.time()
-        while time.time() < start_time + self.timeout:
-            try:
-                fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
-            except BlockingIOError:
-                time.sleep(BROWSER_WAIT_SHORT)
-                continue
-            try:
-                self.driver = selenium.webdriver.Chrome(
-                    options=self.chrome_options,
-                    desired_capabilities=self.chrome_capabilities)
-                self.element_class = splinter.driver.webdriver.WebDriverElement
-                self._cookie_manager = splinter.driver.webdriver.cookie_manager.CookieManager(
-                    self.driver)
-                super(splinter.driver.webdriver.chrome.WebDriver,
-                      self).__init__(2)
-                return super(BlockingBrowser, self).__enter__()
-            except:
-                fcntl.flock(self.lock_file, fcntl.LOCK_UN)
-                self.lock_file.close()
-                raise RuntimeError("Error starting browser. "
-                                   "Releasing lock file.")
-        raise TimeoutError("Could not start chrome browser in time.")
-
-    def __exit__(self, exc_type, exc_value, traceback):
-        """Exit context manager for BlockingBrowser.
-
-        The exit context manager simply calls the parent class exit and
-        releases the lock file.
-        """
-        try:
-            super(BlockingBrowser, self).__exit__(exc_type, exc_value,
-                                                  traceback)
-        except:
-            raise RuntimeError("Failed to quit browser. Releasing lock file.")
-        finally:
-            fcntl.flock(self.lock_file, fcntl.LOCK_UN)
-            self.lock_file.close()
-
-    def restart(self):
-        """Method to restart browser session without releasing lock file."""
-        self.quit()
-        self.__enter__()
-
-    def visit_persistent(self,
-                         url,
-                         page_load_timeout,
-                         num_tries,
-                         backup_url="about:blank",
-                         check_for_element=None):
-        """Method to visit webpages and retry upon failure.
-
-        The function visits a web page and checks the the resulting URL matches
-        the intended URL, i.e. no redirects have happened
-
-        Args:
-            url: the intended url
-            page_load_timeout: timeout for page visits
-            num_tries: number of tries before url is declared unreachable
-            backup_url: url to visit if first url is not reachable. This can be
-            used to simply refresh the browser and try again or to re-login to
-            the AP
-            check_for_element: element id to check for existence on page
-        """
-        self.driver.set_page_load_timeout(page_load_timeout)
-        for idx in range(num_tries):
-            try:
-                self.visit(url)
-            except:
-                self.restart()
-
-            page_reached = self.url.split("/")[-1] == url.split("/")[-1]
-            if check_for_element:
-                time.sleep(BROWSER_WAIT_MED)
-                element = self.find_by_id(check_for_element)
-                if not element:
-                    page_reached = 0
-            if page_reached:
-                break
-            else:
-                try:
-                    self.visit(backup_url)
-                except:
-                    self.restart()
-
-            if idx == num_tries - 1:
-                self.log.error("URL unreachable. Current URL: {}".format(
-                    self.url))
-                raise RuntimeError("URL unreachable.")
-
-
-class WifiRetailAP(object):
-    """Base class implementation for retail ap.
-
-    Base class provides functions whose implementation is shared by all aps.
-    If some functions such as set_power not supported by ap, checks will raise
-    exceptions.
-    """
-    def __init__(self, ap_settings):
-        self.ap_settings = ap_settings.copy()
-        self.log = logger.create_tagged_trace_logger("AccessPoint|{}".format(
-            self._get_control_ip_address()))
-        # Lock AP
-        if self.ap_settings.get('lock_ap', 0):
-            self.lock_timeout = self.ap_settings.get('lock_timeout', 3600)
-            self._lock_ap()
-
-    def teardown(self):
-        """Function to perform destroy operations."""
-        self._unlock_ap()
-
-    def reset(self):
-        """Function that resets AP.
-
-        Function implementation is AP dependent and intended to perform any
-        necessary reset operations as part of controller destroy.
-        """
-        pass
-
-    def read_ap_settings(self):
-        """Function that reads current ap settings.
-
-        Function implementation is AP dependent and thus base class raises exception
-        if function not implemented in child class.
-        """
-        raise NotImplementedError
-
-    def validate_ap_settings(self):
-        """Function to validate ap settings.
-
-        This function compares the actual ap settings read from the web GUI
-        with the assumed settings saved in the AP object. When called after AP
-        configuration, this method helps ensure that our configuration was
-        successful.
-        Note: Calling this function updates the stored ap_settings
-
-        Raises:
-            ValueError: If read AP settings do not match stored settings.
-        """
-        assumed_ap_settings = self.ap_settings.copy()
-        actual_ap_settings = self.read_ap_settings()
-        if assumed_ap_settings != actual_ap_settings:
-            self.log.warning(
-                "Discrepancy in AP settings. Some settings may have been overwritten."
-            )
-
-    def configure_ap(self, **config_flags):
-        """Function that configures ap based on values of ap_settings.
-
-        Function implementation is AP dependent and thus base class raises exception
-        if function not implemented in child class.
-
-        Args:
-            config_flags: optional configuration flags
-        """
-        raise NotImplementedError
-
-    def set_region(self, region):
-        """Function that sets AP region.
-
-        This function sets the region for the AP. Note that this may overwrite
-        channel and bandwidth settings in cases where the new region does not
-        support the current wireless configuration.
-
-        Args:
-            region: string indicating AP region
-        """
-        self.log.warning("Updating region may overwrite wireless settings.")
-        setting_to_update = {"region": region}
-        self.update_ap_settings(setting_to_update)
-
-    def set_radio_on_off(self, network, status):
-        """Function that turns the radio on or off.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            status: boolean indicating on or off (0: off, 1: on)
-        """
-        setting_to_update = {"status_{}".format(network): int(status)}
-        self.update_ap_settings(setting_to_update)
-
-    def set_ssid(self, network, ssid):
-        """Function that sets network SSID.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            ssid: string containing ssid
-        """
-        setting_to_update = {"ssid_{}".format(network): str(ssid)}
-        self.update_ap_settings(setting_to_update)
-
-    def set_channel(self, network, channel):
-        """Function that sets network channel.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            channel: string or int containing channel
-        """
-        setting_to_update = {"channel_{}".format(network): str(channel)}
-        self.update_ap_settings(setting_to_update)
-
-    def set_bandwidth(self, network, bandwidth):
-        """Function that sets network bandwidth/mode.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
-        """
-        setting_to_update = {"bandwidth_{}".format(network): str(bandwidth)}
-        self.update_ap_settings(setting_to_update)
-
-    def set_power(self, network, power):
-        """Function that sets network transmit power.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            power: string containing power level, e.g., 25%, 100%
-        """
-        setting_to_update = {"power_{}".format(network): str(power)}
-        self.update_ap_settings(setting_to_update)
-
-    def set_security(self, network, security_type, *password):
-        """Function that sets network security setting and password.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            security: string containing security setting, e.g., WPA2-PSK
-            password: optional argument containing password
-        """
-        if (len(password) == 1) and (type(password[0]) == str):
-            setting_to_update = {
-                "security_type_{}".format(network): str(security_type),
-                "password_{}".format(network): str(password[0])
-            }
-        else:
-            setting_to_update = {
-                "security_type_{}".format(network): str(security_type)
-            }
-        self.update_ap_settings(setting_to_update)
-
-    def set_rate(self):
-        """Function that configures rate used by AP.
-
-        Function implementation is not supported by most APs and thus base
-        class raises exception if function not implemented in child class.
-        """
-        raise NotImplementedError
-
-    def update_ap_settings(self, dict_settings={}, **named_settings):
-        """Function to update settings of existing AP.
-
-        Function copies arguments into ap_settings and calls configure_retail_ap
-        to apply them.
-
-        Args:
-            *dict_settings accepts single dictionary of settings to update
-            **named_settings accepts named settings to update
-            Note: dict and named_settings cannot contain the same settings.
-        """
-        settings_to_update = dict(dict_settings, **named_settings)
-        if len(settings_to_update) != len(dict_settings) + len(named_settings):
-            raise KeyError("The following keys were passed twice: {}".format(
-                (set(dict_settings.keys()).intersection(
-                    set(named_settings.keys())))))
-        if not set(settings_to_update.keys()).issubset(
-                set(self.ap_settings.keys())):
-            raise KeyError(
-                "The following settings are invalid for this AP: {}".format(
-                    set(settings_to_update.keys()).difference(
-                        set(self.ap_settings.keys()))))
-
-        updates_requested = False
-        status_toggle_flag = False
-        for setting, value in settings_to_update.items():
-            if self.ap_settings[setting] != value:
-                self.ap_settings[setting] = value
-                if "status" in setting:
-                    status_toggle_flag = True
-                updates_requested = True
-
-        if updates_requested:
-            self.configure_ap(status_toggled=status_toggle_flag)
-
-    def band_lookup_by_channel(self, channel):
-        """Function that gives band name by channel number.
-
-        Args:
-            channel: channel number to lookup
-        Returns:
-            band: name of band which this channel belongs to on this ap
-        """
-        for key, value in self.channel_band_map.items():
-            if channel in value:
-                return key
-        raise ValueError("Invalid channel passed in argument.")
-
-    def _get_control_ip_address(self):
-        """Function to get AP's Control Interface IP address."""
-        if "ssh_config" in self.ap_settings.keys():
-            return self.ap_settings["ssh_config"]["host"]
-        else:
-            return self.ap_settings["ip_address"]
-
-    def _lock_ap(self):
-        """Function to lock the ap while tests are running."""
-        self.lock_file_path = "/tmp/{}_{}_{}.lock".format(
-            self.ap_settings['brand'], self.ap_settings['model'],
-            self._get_control_ip_address())
-        if not os.path.exists(self.lock_file_path):
-            with open(self.lock_file_path, 'w'):
-                pass
-        self.lock_file = open(self.lock_file_path, "r")
-        start_time = time.time()
-        self.log.info('Trying to acquire AP lock.')
-        while time.time() < start_time + self.lock_timeout:
-            try:
-                fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
-            except BlockingIOError:
-                time.sleep(BROWSER_WAIT_SHORT)
-                continue
-            self.log.info('AP lock acquired.')
-            return
-        raise RuntimeError("Could not lock AP in time.")
-
-    def _unlock_ap(self):
-        """Function to unlock the AP when tests are done."""
-        self.log.info('Releasing AP lock.')
-        if hasattr(self, "lock_file"):
-            fcntl.flock(self.lock_file, fcntl.LOCK_UN)
-            self.lock_file.close()
-
-
-class NetgearR7000AP(WifiRetailAP):
-    """Class that implements Netgear R7000 AP."""
-    def __init__(self, ap_settings):
-        super().__init__(ap_settings)
-        self.init_gui_data()
-        # Read and update AP settings
-        self.read_ap_settings()
-        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
-            self.update_ap_settings(ap_settings)
-
-    def init_gui_data(self):
-        """Function to initialize data used while interacting with web GUI"""
-        self.config_page = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_nologin = (
-            "{protocol}://{ip_address}:{port}/"
-            "WLG_wireless_dual_band_r10.htm").format(
-                protocol=self.ap_settings["protocol"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_advanced = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_adv_dual_band2.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.networks = ["2G", "5G_1"]
-        self.channel_band_map = {
-            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
-            "5G_1": [
-                36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
-                124, 128, 132, 136, 140, 149, 153, 157, 161, 165
-            ]
-        }
-        self.region_map = {
-            "1": "Africa",
-            "2": "Asia",
-            "3": "Australia",
-            "4": "Canada",
-            "5": "Europe",
-            "6": "Israel",
-            "7": "Japan",
-            "8": "Korea",
-            "9": "Mexico",
-            "10": "South America",
-            "11": "United States",
-            "12": "Middle East(Algeria/Syria/Yemen)",
-            "14": "Russia",
-            "16": "China",
-            "17": "India",
-            "18": "Malaysia",
-            "19": "Middle East(Iran/Labanon/Qatar)",
-            "20": "Middle East(Turkey/Egypt/Tunisia/Kuwait)",
-            "21": "Middle East(Saudi Arabia)",
-            "22": "Middle East(United Arab Emirates)",
-            "23": "Singapore",
-            "24": "Taiwan"
-        }
-        self.config_page_fields = {
-            "region": "WRegion",
-            ("2G", "status"): "enable_ap",
-            ("5G_1", "status"): "enable_ap_an",
-            ("2G", "ssid"): "ssid",
-            ("5G_1", "ssid"): "ssid_an",
-            ("2G", "channel"): "w_channel",
-            ("5G_1", "channel"): "w_channel_an",
-            ("2G", "bandwidth"): "opmode",
-            ("5G_1", "bandwidth"): "opmode_an",
-            ("2G", "power"): "enable_tpc",
-            ("5G_1", "power"): "enable_tpc_an",
-            ("2G", "security_type"): "security_type",
-            ("5G_1", "security_type"): "security_type_an",
-            ("2G", "password"): "passphrase",
-            ("5G_1", "password"): "passphrase_an"
-        }
-        self.bw_mode_values = {
-            "g and b": "11g",
-            "145Mbps": "VHT20",
-            "300Mbps": "VHT40",
-            "HT80": "VHT80"
-        }
-        self.power_mode_values = {
-            "1": "100%",
-            "2": "75%",
-            "3": "50%",
-            "4": "25%"
-        }
-        self.bw_mode_text = {
-            "11g": "Up to 54 Mbps",
-            "VHT20": "Up to 289 Mbps",
-            "VHT40": "Up to 600 Mbps",
-            "VHT80": "Up to 1300 Mbps"
-        }
-
-    def read_ap_settings(self):
-        """Function to read ap settings."""
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            # Visit URL
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
-
-            for key, value in self.config_page_fields.items():
-                if "status" in key:
-                    browser.visit_persistent(self.config_page_advanced,
-                                             BROWSER_WAIT_MED, 10)
-                    config_item = browser.find_by_name(value)
-                    self.ap_settings["{}_{}".format(key[1], key[0])] = int(
-                        config_item.first.checked)
-                    browser.visit_persistent(self.config_page,
-                                             BROWSER_WAIT_MED, 10)
-                else:
-                    config_item = browser.find_by_name(value)
-                    if "bandwidth" in key:
-                        self.ap_settings["{}_{}".format(
-                            key[1], key[0])] = self.bw_mode_values[
-                                config_item.first.value]
-                    elif "power" in key:
-                        self.ap_settings["{}_{}".format(
-                            key[1], key[0])] = self.power_mode_values[
-                                config_item.first.value]
-                    elif "region" in key:
-                        self.ap_settings["region"] = self.region_map[
-                            config_item.first.value]
-                    elif "security_type" in key:
-                        for item in config_item:
-                            if item.checked:
-                                self.ap_settings["{}_{}".format(
-                                    key[1], key[0])] = item.value
-                    else:
-                        config_item = browser.find_by_name(value)
-                        self.ap_settings["{}_{}".format(
-                            key[1], key[0])] = config_item.first.value
-        return self.ap_settings.copy()
-
-    def configure_ap(self, **config_flags):
-        """Function to configure ap wireless settings."""
-        # Turn radios on or off
-        if config_flags["status_toggled"]:
-            self.configure_radio_on_off()
-        # Configure radios
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            # Visit URL
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
-            browser.visit_persistent(self.config_page_nologin,
-                                     BROWSER_WAIT_MED, 10, self.config_page)
-
-            # Update region, and power/bandwidth for each network
-            config_item = browser.find_by_name(
-                self.config_page_fields["region"]).first
-            config_item.select_by_text(self.ap_settings["region"])
-            for key, value in self.config_page_fields.items():
-                if "power" in key:
-                    config_item = browser.find_by_name(value).first
-                    config_item.select_by_text(self.ap_settings["{}_{}".format(
-                        key[1], key[0])])
-                elif "bandwidth" in key:
-                    config_item = browser.find_by_name(value).first
-                    try:
-                        config_item.select_by_text(
-                            self.bw_mode_text[self.ap_settings["{}_{}".format(
-                                key[1], key[0])]])
-                    except AttributeError:
-                        self.log.warning(
-                            "Cannot select bandwidth. Keeping AP default.")
-
-            # Update security settings (passwords updated only if applicable)
-            for key, value in self.config_page_fields.items():
-                if "security_type" in key:
-                    browser.choose(
-                        value, self.ap_settings["{}_{}".format(key[1],
-                                                               key[0])])
-                    if self.ap_settings["{}_{}".format(key[1],
-                                                       key[0])] == "WPA2-PSK":
-                        config_item = browser.find_by_name(
-                            self.config_page_fields[(key[0],
-                                                     "password")]).first
-                        config_item.fill(self.ap_settings["{}_{}".format(
-                            "password", key[0])])
-
-            # Update SSID and channel for each network
-            # NOTE: Update ordering done as such as workaround for R8000
-            # wherein channel and SSID get overwritten when some other
-            # variables are changed. However, region does have to be set before
-            # channel in all cases.
-            for key, value in self.config_page_fields.items():
-                if "ssid" in key:
-                    config_item = browser.find_by_name(value).first
-                    config_item.fill(self.ap_settings["{}_{}".format(
-                        key[1], key[0])])
-                elif "channel" in key:
-                    config_item = browser.find_by_name(value).first
-                    try:
-                        config_item.select(self.ap_settings["{}_{}".format(
-                            key[1], key[0])])
-                        time.sleep(BROWSER_WAIT_SHORT)
-                    except AttributeError:
-                        self.log.warning(
-                            "Cannot select channel. Keeping AP default.")
-                    try:
-                        alert = browser.get_alert()
-                        alert.accept()
-                    except:
-                        pass
-
-            time.sleep(BROWSER_WAIT_SHORT)
-            browser.find_by_name("Apply").first.click()
-            time.sleep(BROWSER_WAIT_SHORT)
-            try:
-                alert = browser.get_alert()
-                alert.accept()
-                time.sleep(BROWSER_WAIT_SHORT)
-            except:
-                time.sleep(BROWSER_WAIT_SHORT)
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
-                                     10)
-
-    def configure_radio_on_off(self):
-        """Helper configuration function to turn radios on/off."""
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            # Visit URL
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
-            browser.visit_persistent(self.config_page_advanced,
-                                     BROWSER_WAIT_MED, 10)
-
-            # Turn radios on or off
-            for key, value in self.config_page_fields.items():
-                if "status" in key:
-                    config_item = browser.find_by_name(value).first
-                    if self.ap_settings["{}_{}".format(key[1], key[0])]:
-                        config_item.check()
-                    else:
-                        config_item.uncheck()
-
-            time.sleep(BROWSER_WAIT_SHORT)
-            browser.find_by_name("Apply").first.click()
-            time.sleep(BROWSER_WAIT_EXTRA_LONG)
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
-                                     10)
-
-
-class NetgearR7000NAAP(NetgearR7000AP):
-    """Class that implements Netgear R7000 NA AP."""
-    def init_gui_data(self):
-        """Function to initialize data used while interacting with web GUI"""
-        super.init_gui_data()
-        self.region_map["11"] = "North America"
-
-
-class NetgearR7500AP(WifiRetailAP):
-    """Class that implements Netgear R7500 AP."""
-    def __init__(self, ap_settings):
-        super().__init__(ap_settings)
-        self.init_gui_data()
-        # Read and update AP settings
-        self.read_ap_settings()
-        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
-            self.update_ap_settings(ap_settings)
-
-    def init_gui_data(self):
-        """Function to initialize data used while interacting with web GUI"""
-        self.config_page = ("{protocol}://{username}:{password}@"
-                            "{ip_address}:{port}/index.htm").format(
-                                protocol=self.ap_settings["protocol"],
-                                username=self.ap_settings["admin_username"],
-                                password=self.ap_settings["admin_password"],
-                                ip_address=self.ap_settings["ip_address"],
-                                port=self.ap_settings["port"])
-        self.config_page_advanced = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/adv_index.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.networks = ["2G", "5G_1"]
-        self.channel_band_map = {
-            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
-            "5G_1": [
-                36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
-                124, 128, 132, 136, 140, 149, 153, 157, 161, 165
-            ]
-        }
-        self.config_page_fields = {
-            "region": "WRegion",
-            ("2G", "status"): "enable_ap",
-            ("5G_1", "status"): "enable_ap_an",
-            ("2G", "ssid"): "ssid",
-            ("5G_1", "ssid"): "ssid_an",
-            ("2G", "channel"): "w_channel",
-            ("5G_1", "channel"): "w_channel_an",
-            ("2G", "bandwidth"): "opmode",
-            ("5G_1", "bandwidth"): "opmode_an",
-            ("2G", "security_type"): "security_type",
-            ("5G_1", "security_type"): "security_type_an",
-            ("2G", "password"): "passphrase",
-            ("5G_1", "password"): "passphrase_an"
-        }
-        self.region_map = {
-            "0": "Africa",
-            "1": "Asia",
-            "2": "Australia",
-            "3": "Canada",
-            "4": "Europe",
-            "5": "Israel",
-            "6": "Japan",
-            "7": "Korea",
-            "8": "Mexico",
-            "9": "South America",
-            "10": "United States",
-            "11": "China",
-            "12": "India",
-            "13": "Malaysia",
-            "14": "Middle East(Algeria/Syria/Yemen)",
-            "15": "Middle East(Iran/Labanon/Qatar)",
-            "16": "Middle East(Turkey/Egypt/Tunisia/Kuwait)",
-            "17": "Middle East(Saudi Arabia)",
-            "18": "Middle East(United Arab Emirates)",
-            "19": "Russia",
-            "20": "Singapore",
-            "21": "Taiwan"
-        }
-        self.bw_mode_text_2g = {
-            "11g": "Up to 54 Mbps",
-            "VHT20": "Up to 289 Mbps",
-            "VHT40": "Up to 600 Mbps"
-        }
-        self.bw_mode_text_5g = {
-            "VHT20": "Up to 347 Mbps",
-            "VHT40": "Up to 800 Mbps",
-            "VHT80": "Up to 1733 Mbps"
-        }
-        self.bw_mode_values = {
-            "1": "11g",
-            "2": "VHT20",
-            "3": "VHT40",
-            "7": "VHT20",
-            "8": "VHT40",
-            "9": "VHT80"
-        }
-
-    def read_ap_settings(self):
-        """Function to read ap wireless settings."""
-        # Get radio status (on/off)
-        self.read_radio_on_off()
-        # Get radio configuration. Note that if both radios are off, the below
-        # code will result in an error
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            browser.visit_persistent(self.config_page,
-                                     BROWSER_WAIT_MED,
-                                     10,
-                                     check_for_element="wireless")
-            wireless_button = browser.find_by_id("wireless").first
-            wireless_button.click()
-            time.sleep(BROWSER_WAIT_MED)
-
-            with browser.get_iframe("formframe") as iframe:
-                for key, value in self.config_page_fields.items():
-                    if "bandwidth" in key:
-                        config_item = iframe.find_by_name(value).first
-                        self.ap_settings["{}_{}".format(
-                            key[1],
-                            key[0])] = self.bw_mode_values[config_item.value]
-                    elif "region" in key:
-                        config_item = iframe.find_by_name(value).first
-                        self.ap_settings["region"] = self.region_map[
-                            config_item.value]
-                    elif "password" in key:
-                        try:
-                            config_item = iframe.find_by_name(value).first
-                            self.ap_settings["{}_{}".format(
-                                key[1], key[0])] = config_item.value
-                            self.ap_settings["{}_{}".format(
-                                "security_type", key[0])] = "WPA2-PSK"
-                        except:
-                            self.ap_settings["{}_{}".format(
-                                key[1], key[0])] = "defaultpassword"
-                            self.ap_settings["{}_{}".format(
-                                "security_type", key[0])] = "Disable"
-                    elif ("channel" in key) or ("ssid" in key):
-                        config_item = iframe.find_by_name(value).first
-                        self.ap_settings["{}_{}".format(
-                            key[1], key[0])] = config_item.value
-                    else:
-                        pass
-        return self.ap_settings.copy()
-
-    def configure_ap(self, **config_flags):
-        """Function to configure ap wireless settings."""
-        # Turn radios on or off
-        if config_flags["status_toggled"]:
-            self.configure_radio_on_off()
-        # Configure radios
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            browser.visit_persistent(self.config_page,
-                                     BROWSER_WAIT_MED,
-                                     10,
-                                     check_for_element="wireless")
-            wireless_button = browser.find_by_id("wireless").first
-            wireless_button.click()
-            time.sleep(BROWSER_WAIT_MED)
-
-            with browser.get_iframe("formframe") as iframe:
-                # Update AP region. Must be done before channel setting
-                config_item = iframe.find_by_name(
-                    self.config_page_fields["region"]).first
-                config_item.select_by_text(self.ap_settings["region"])
-                # Update wireless settings for each network
-                for key, value in self.config_page_fields.items():
-                    if "ssid" in key:
-                        config_item = iframe.find_by_name(value).first
-                        config_item.fill(self.ap_settings["{}_{}".format(
-                            key[1], key[0])])
-                    elif "channel" in key:
-                        channel_string = "0" * (int(self.ap_settings[
-                            "{}_{}".format(key[1], key[0])]) < 10) + str(
-                                self.ap_settings["{}_{}".format(
-                                    key[1], key[0])]) + "(DFS)" * (48 < int(
-                                        self.ap_settings["{}_{}".format(
-                                            key[1], key[0])]) < 149)
-                        config_item = iframe.find_by_name(value).first
-                        try:
-                            config_item.select_by_text(channel_string)
-                        except AttributeError:
-                            self.log.warning(
-                                "Cannot select channel. Keeping AP default.")
-                    elif key == ("2G", "bandwidth"):
-                        config_item = iframe.find_by_name(value).first
-                        try:
-                            config_item.select_by_text(
-                                str(self.bw_mode_text_2g[self.ap_settings[
-                                    "{}_{}".format(key[1], key[0])]]))
-                        except AttributeError:
-                            self.log.warning(
-                                "Cannot select bandwidth. Keeping AP default.")
-                    elif key == ("5G_1", "bandwidth"):
-                        config_item = iframe.find_by_name(value).first
-                        try:
-                            config_item.select_by_text(
-                                str(self.bw_mode_text_5g[self.ap_settings[
-                                    "{}_{}".format(key[1], key[0])]]))
-                        except AttributeError:
-                            self.log.warning(
-                                "Cannot select bandwidth. Keeping AP default.")
-                # Update passwords for WPA2-PSK protected networks
-                # (Must be done after security type is selected)
-                for key, value in self.config_page_fields.items():
-                    if "security_type" in key:
-                        iframe.choose(
-                            value,
-                            self.ap_settings["{}_{}".format(key[1], key[0])])
-                        if self.ap_settings["{}_{}".format(
-                                key[1], key[0])] == "WPA2-PSK":
-                            config_item = iframe.find_by_name(
-                                self.config_page_fields[(key[0],
-                                                         "password")]).first
-                            config_item.fill(self.ap_settings["{}_{}".format(
-                                "password", key[0])])
-
-                apply_button = iframe.find_by_name("Apply")
-                apply_button[0].click()
-                time.sleep(BROWSER_WAIT_SHORT)
-                try:
-                    alert = browser.get_alert()
-                    alert.accept()
-                except:
-                    pass
-                time.sleep(BROWSER_WAIT_SHORT)
-                try:
-                    alert = browser.get_alert()
-                    alert.accept()
-                except:
-                    pass
-                time.sleep(BROWSER_WAIT_SHORT)
-            time.sleep(BROWSER_WAIT_EXTRA_LONG)
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
-                                     10)
-
-    def configure_radio_on_off(self):
-        """Helper configuration function to turn radios on/off."""
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
-            browser.visit_persistent(self.config_page_advanced,
-                                     BROWSER_WAIT_MED,
-                                     10,
-                                     check_for_element="advanced_bt")
-            advanced_button = browser.find_by_id("advanced_bt").first
-            advanced_button.click()
-            time.sleep(BROWSER_WAIT_MED)
-            wireless_button = browser.find_by_id("wladv").first
-            wireless_button.click()
-            time.sleep(BROWSER_WAIT_MED)
-
-            with browser.get_iframe("formframe") as iframe:
-                # Turn radios on or off
-                for key, value in self.config_page_fields.items():
-                    if "status" in key:
-                        config_item = iframe.find_by_name(value).first
-                        if self.ap_settings["{}_{}".format(key[1], key[0])]:
-                            config_item.check()
-                        else:
-                            config_item.uncheck()
-
-                time.sleep(BROWSER_WAIT_SHORT)
-                browser.find_by_name("Apply").first.click()
-                time.sleep(BROWSER_WAIT_EXTRA_LONG)
-                browser.visit_persistent(self.config_page,
-                                         BROWSER_WAIT_EXTRA_LONG, 10)
-
-    def read_radio_on_off(self):
-        """Helper configuration function to read radio status."""
-        with BlockingBrowser(self.ap_settings["headless_browser"],
-                             900) as browser:
-            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
-            browser.visit_persistent(self.config_page_advanced,
-                                     BROWSER_WAIT_MED,
-                                     10,
-                                     check_for_element="advanced_bt")
-            advanced_button = browser.find_by_id("advanced_bt").first
-            advanced_button.click()
-            time.sleep(BROWSER_WAIT_SHORT)
-            wireless_button = browser.find_by_id("wladv").first
-            wireless_button.click()
-            time.sleep(BROWSER_WAIT_MED)
-
-            with browser.get_iframe("formframe") as iframe:
-                # Turn radios on or off
-                for key, value in self.config_page_fields.items():
-                    if "status" in key:
-                        config_item = iframe.find_by_name(value).first
-                        self.ap_settings["{}_{}".format(key[1], key[0])] = int(
-                            config_item.checked)
-
-
-class NetgearR7800AP(NetgearR7500AP):
-    """Class that implements Netgear R7800 AP.
-
-    Since most of the class' implementation is shared with the R7500, this
-    class inherits from NetgearR7500AP and simply redefines config parameters
-    """
-    def __init__(self, ap_settings):
-        super().__init__(ap_settings)
-        self.init_gui_data()
-        # Overwrite minor differences from R7500 AP
-        self.bw_mode_text_2g["VHT20"] = "Up to 347 Mbps"
-        # Read and update AP settings
-        self.read_ap_settings()
-        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
-            self.update_ap_settings(ap_settings)
-
-
-class NetgearR8000AP(NetgearR7000AP):
-    """Class that implements Netgear R8000 AP.
-
-    Since most of the class' implementation is shared with the R7000, this
-    class inherits from NetgearR7000AP and simply redefines config parameters
-    """
-    def init_gui_data(self):
-        super().init_gui_data()
-        # Overwrite minor differences from R7000 AP
-        self.config_page = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_wireless_dual_band_r8000.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_nologin = (
-            "{protocol}://{ip_address}:{port}/"
-            "WLG_wireless_dual_band_r8000.htm").format(
-                protocol=self.ap_settings["protocol"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_advanced = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_adv_dual_band2_r8000.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.networks = ["2G", "5G_1", "5G_2"]
-        self.channel_band_map = {
-            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
-            "5G_1": [36, 40, 44, 48],
-            "5G_2": [149, 153, 157, 161, 165]
-        }
-        self.config_page_fields = {
-            "region": "WRegion",
-            ("2G", "status"): "enable_ap",
-            ("5G_1", "status"): "enable_ap_an",
-            ("5G_2", "status"): "enable_ap_an_2",
-            ("2G", "ssid"): "ssid",
-            ("5G_1", "ssid"): "ssid_an",
-            ("5G_2", "ssid"): "ssid_an_2",
-            ("2G", "channel"): "w_channel",
-            ("5G_1", "channel"): "w_channel_an",
-            ("5G_2", "channel"): "w_channel_an_2",
-            ("2G", "bandwidth"): "opmode",
-            ("5G_1", "bandwidth"): "opmode_an",
-            ("5G_2", "bandwidth"): "opmode_an_2",
-            ("2G", "security_type"): "security_type",
-            ("5G_1", "security_type"): "security_type_an",
-            ("5G_2", "security_type"): "security_type_an_2",
-            ("2G", "password"): "passphrase",
-            ("5G_1", "password"): "passphrase_an",
-            ("5G_2", "password"): "passphrase_an_2"
-        }
-
-
-class NetgearR8500AP(NetgearR7000AP):
-    """Class that implements Netgear R8500 AP.
-
-    Since most of the class' implementation is shared with the R7000, this
-    class inherits from NetgearR7000AP and simply redefines config parameters
-    """
-    def __init__(self, ap_settings):
-        super().__init__(ap_settings)
-        self.init_gui_data()
-        # Overwrite minor differences from R8000 AP
-        self.config_page = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_wireless_tri_band.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_nologin = (
-            "{protocol}://{ip_address}:{port}/"
-            "WLG_wireless_tri_band.htm").format(
-                protocol=self.ap_settings["protocol"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_advanced = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_adv_tri_band2.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.networks = ["2G", "5G_1", "5G_2"]
-        self.channel_band_map = {
-            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
-            "5G_1": [36, 40, 44, 48],
-            "5G_2": [149, 153, 157, 161, 165]
-        }
-        self.config_page_fields = {
-            "region": "WRegion",
-            ("2G", "status"): "enable_ap",
-            ("5G_1", "status"): "enable_ap_an",
-            ("5G_2", "status"): "enable_ap_an_2",
-            ("2G", "ssid"): "ssid",
-            ("5G_1", "ssid"): "ssid_an",
-            ("5G_2", "ssid"): "ssid_an_2",
-            ("2G", "channel"): "w_channel",
-            ("5G_1", "channel"): "w_channel_an",
-            ("5G_2", "channel"): "w_channel_an_2",
-            ("2G", "bandwidth"): "opmode",
-            ("5G_1", "bandwidth"): "opmode_an",
-            ("5G_2", "bandwidth"): "opmode_an_2",
-            ("2G", "security_type"): "security_type",
-            ("5G_1", "security_type"): "security_type_an",
-            ("5G_2", "security_type"): "security_type_an_2",
-            ("2G", "password"): "passphrase",
-            ("5G_1", "password"): "passphrase_an",
-            ("5G_2", "password"): "passphrase_an_2"
-        }
-        self.bw_mode_text = {
-            "11g": "Up to 54 Mbps",
-            "VHT20": "Up to 433 Mbps",
-            "VHT40": "Up to 1000 Mbps",
-            "VHT80": "Up to 2165 Mbps"
-        }
-        # Read and update AP settings
-        self.read_ap_settings()
-        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
-            self.update_ap_settings(ap_settings)
-
-
-class NetgearRAXAP(NetgearR7000AP):
-    """Class that implements Netgear RAX AP.
-
-    Since most of the class' implementation is shared with the R7000, this
-    class inherits from NetgearR7000AP and simply redefines config parameters
-    """
-    def init_gui_data(self):
-        super().init_gui_data()
-        # Overwrite minor differences from R7000 AP
-        self.config_page = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_nologin = (
-            "{protocol}://{ip_address}:{port}/"
-            "WLG_wireless_dual_band_r10.htm").format(
-                protocol=self.ap_settings["protocol"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.config_page_advanced = (
-            "{protocol}://{username}:{password}@"
-            "{ip_address}:{port}/WLG_adv_dual_band2.htm").format(
-                protocol=self.ap_settings["protocol"],
-                username=self.ap_settings["admin_username"],
-                password=self.ap_settings["admin_password"],
-                ip_address=self.ap_settings["ip_address"],
-                port=self.ap_settings["port"])
-        self.networks = ["2G", "5G_1", "5G_2"]
-        self.channel_band_map = {
-            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
-            "5G_1": [
-                36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
-                124, 128, 132, 136, 140, 149, 153, 157, 161, 165
-            ]
-        }
-
-        self.bw_mode_values = {
-            "g and b": "11g",
-            "145Mbps": "VHT20",
-            "300Mbps": "VHT40",
-            "HT80": "VHT80",
-            "HT160": "VHT160"
-        }
-        self.bw_mode_text = {
-            "11g": "Up to 54 Mbps",
-            "VHT20": "Up to 600 Mbps",
-            "VHT40": "Up to 1200 Mbps",
-            "VHT80": "Up to 2400 Mbps",
-            "VHT160": "Up to 4800 Mbps"
-        }
-
-
-class GoogleWifiAP(WifiRetailAP):
-    """ Class that implements Google Wifi AP.
-
-    This class is a work in progress
-    """
-    def __init__(self, ap_settings):
-        super().__init__(ap_settings)
-        # Initialize AP
-        if self.ap_settings["status_2G"] and self.ap_settings["status_5G_1"]:
-            raise ValueError("Error initializing Google Wifi AP. "
-                             "Only one interface can be enabled at a time.")
-        self.channel_band_map = {
-            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
-            "5G_1": [36, 40, 44, 48, 149, 153, 157, 161, 165]
-        }
-        self.BW_MODE_MAP = {
-            "legacy": 20,
-            "VHT20": 20,
-            "VHT40": 40,
-            "VHT80": 80
-        }
-        self.default_settings = {
-            "region": "United States",
-            "brand": "Google",
-            "model": "Wifi",
-            "hostapd_profile": "whirlwind",
-            "status_2G": 0,
-            "status_5G_1": 0,
-            "ssid_2G": "GoogleWifi_2G",
-            "ssid_5G_1": "GoogleWifi_5G",
-            "channel_2G": 11,
-            "channel_5G_1": 149,
-            "bandwidth_2G": "VHT20",
-            "bandwidth_5G_1": "VHT20",
-            "power_2G": "auto",
-            "power_5G_1": "auto",
-            "mode_2G": None,
-            "num_streams_2G": None,
-            "rate_2G": "auto",
-            "short_gi_2G": 0,
-            "mode_5G_1": None,
-            "num_streams_5G_1": None,
-            "rate_5G_1": "auto",
-            "short_gi_5G_1": 0,
-            "security_type_2G": "Open",
-            "security_type_5G_1": "Open",
-            "subnet_2G": "192.168.1.0/24",
-            "subnet_5G_1": "192.168.9.0/24",
-            "password_2G": "password",
-            "password_5G_1": "password"
-        }
-
-        for setting in self.default_settings.keys():
-            if setting not in self.ap_settings:
-                self.log.debug(
-                    "{0} not found during init. Setting {0} = {1}".format(
-                        setting, self.default_settings[setting]))
-                self.ap_settings[setting] = self.default_settings[setting]
-        init_settings = self.ap_settings.copy()
-        init_settings["ap_subnet"] = {
-            "2g": self.ap_settings["subnet_2G"],
-            "5g": self.ap_settings["subnet_5G_1"]
-        }
-        self.access_point = access_point.AccessPoint(init_settings)
-        self.configure_ap()
-
-    def read_ap_settings(self):
-        """Function that reads current ap settings."""
-        return self.ap_settings.copy()
-
-    def update_ap_settings(self, dict_settings={}, **named_settings):
-        """Function to update settings of existing AP.
-
-        Function copies arguments into ap_settings and calls configure_ap
-        to apply them.
-
-        Args:
-            dict_settings: single dictionary of settings to update
-            **named_settings: named settings to update
-            Note: dict and named_settings cannot contain the same settings.
-        """
-        settings_to_update = dict(dict_settings, **named_settings)
-        if len(settings_to_update) != len(dict_settings) + len(named_settings):
-            raise KeyError("The following keys were passed twice: {}".format(
-                (set(dict_settings.keys()).intersection(
-                    set(named_settings.keys())))))
-        if not set(settings_to_update.keys()).issubset(
-                set(self.ap_settings.keys())):
-            raise KeyError(
-                "The following settings are invalid for this AP: {}".format(
-                    set(settings_to_update.keys()).difference(
-                        set(self.ap_settings.keys()))))
-
-        updating_2G = any(["2G" in x for x in settings_to_update.keys()])
-        updating_5G_1 = any(["5G_1" in x for x in settings_to_update.keys()])
-        if updating_2G and updating_5G_1:
-            raise ValueError(
-                "Error updating Google WiFi AP. "
-                "One interface can be activated and updated at a time")
-        elif updating_2G:
-            # If updating an interface and not explicitly setting its status,
-            # it is assumed that the interface is to be ENABLED and updated
-            if "status_2G" not in settings_to_update:
-                settings_to_update["status_2G"] = 1
-                settings_to_update["status_5G_1"] = 0
-        elif updating_5G_1:
-            if "status_5G_1" not in settings_to_update:
-                settings_to_update["status_2G"] = 0
-                settings_to_update["status_5G_1"] = 1
-
-        updates_requested = False
-        for setting, value in settings_to_update.items():
-            if self.ap_settings[setting] != value:
-                self.ap_settings[setting] = value
-                updates_requested = True
-
-        if updates_requested:
-            self.configure_ap()
-
-    def configure_ap(self):
-        """Function to configure Google Wifi."""
-        self.log.info("Stopping Google Wifi interfaces.")
-        self.access_point.stop_all_aps()
-
-        if self.ap_settings["status_2G"] == 1:
-            network = "2G"
-            self.log.info("Bringing up 2.4 GHz network.")
-        elif self.ap_settings["status_5G_1"] == 1:
-            network = "5G_1"
-            self.log.info("Bringing up 5 GHz network.")
-        else:
-            return
-
-        bss_settings = []
-        ssid = self.ap_settings["ssid_{}".format(network)]
-        security_mode = self.ap_settings["security_type_{}".format(
-            network)].lower()
-        if "wpa" in security_mode:
-            password = self.ap_settings["password_{}".format(network)]
-            security = hostapd_security.Security(security_mode=security_mode,
-                                                 password=password)
-        else:
-            security = hostapd_security.Security(security_mode=None,
-                                                 password=None)
-        channel = int(self.ap_settings["channel_{}".format(network)])
-        bandwidth = self.BW_MODE_MAP[self.ap_settings["bandwidth_{}".format(
-            network)]]
-        config = hostapd_ap_preset.create_ap_preset(
-            channel=channel,
-            ssid=ssid,
-            security=security,
-            bss_settings=bss_settings,
-            vht_bandwidth=bandwidth,
-            profile_name=self.ap_settings["hostapd_profile"],
-            iface_wlan_2g=self.access_point.wlan_2g,
-            iface_wlan_5g=self.access_point.wlan_5g)
-        config_bridge = self.access_point.generate_bridge_configs(channel)
-        brconfigs = bridge_interface.BridgeInterfaceConfigs(
-            config_bridge[0], "lan0", config_bridge[2])
-        self.access_point.bridge.startup(brconfigs)
-        self.access_point.start_ap(config)
-        self.set_power(network, self.ap_settings["power_{}".format(network)])
-        self.set_rate(
-            network,
-            mode=self.ap_settings["mode_{}".format(network)],
-            num_streams=self.ap_settings["num_streams_{}".format(network)],
-            rate=self.ap_settings["rate_{}".format(network)],
-            short_gi=self.ap_settings["short_gi_{}".format(network)])
-        self.log.info("AP started on channel {} with SSID {}".format(
-            channel, ssid))
-
-    def set_power(self, network, power):
-        """Function that sets network transmit power.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            power: power level in dBm
-        """
-        if power == "auto":
-            power_string = "auto"
-        else:
-            if not float(power).is_integer():
-                self.log.info(
-                    "Power in dBm must be an integer. Setting to {}".format(
-                        int(power)))
-            power = int(power)
-            power_string = "fixed {}".format(int(power) * 100)
-
-        if "2G" in network:
-            interface = self.access_point.wlan_2g
-            self.ap_settings["power_2G"] = power
-        elif "5G_1" in network:
-            interface = self.access_point.wlan_5g
-            self.ap_settings["power_5G_1"] = power
-        self.access_point.ssh.run("iw dev {} set txpower {}".format(
-            interface, power_string))
-
-    def set_rate(self,
-                 network,
-                 mode=None,
-                 num_streams=None,
-                 rate='auto',
-                 short_gi=0):
-        """Function that sets rate.
-
-        Args:
-            network: string containing network identifier (2G, 5G_1, 5G_2)
-            mode: string indicating the WiFi standard to use
-            num_streams: number of MIMO streams. used only for VHT
-            rate: data rate of MCS index to use
-            short_gi: boolean controlling the use of short guard interval
-        """
-        if "2G" in network:
-            interface = self.access_point.wlan_2g
-            interface_short = "2.4"
-            self.ap_settings["mode_2G"] = mode
-            self.ap_settings["num_streams_2G"] = num_streams
-            self.ap_settings["rate_2G"] = rate
-            self.ap_settings["short_gi_2G"] = short_gi
-        elif "5G_1" in network:
-            interface = self.access_point.wlan_5g
-            interface_short = "5"
-            self.ap_settings["mode_5G_1"] = mode
-            self.ap_settings["num_streams_5G_1"] = num_streams
-            self.ap_settings["rate_5G_1"] = rate
-            self.ap_settings["short_gi_5G_1"] = short_gi
-
-        if rate == "auto":
-            cmd_string = "iw dev {0} set bitrates".format(interface)
-        elif "legacy" in mode.lower():
-            cmd_string = "iw dev {0} set bitrates legacy-{1} {2} ht-mcs-{1} vht-mcs-{1}".format(
-                interface, interface_short, rate)
-        elif "vht" in mode.lower():
-            cmd_string = "iw dev {0} set bitrates legacy-{1} ht-mcs-{1} vht-mcs-{1} {2}:{3}".format(
-                interface, interface_short, num_streams, rate)
-            if short_gi:
-                cmd_string = cmd_string + " sgi-{}".format(interface_short)
-        elif "ht" in mode.lower():
-            cmd_string = "iw dev {0} set bitrates legacy-{1} ht-mcs-{1} {2} vht-mcs-{1}".format(
-                interface, interface_short, rate)
-            if short_gi:
-                cmd_string = cmd_string + " sgi-{}".format(interface_short)
-        self.access_point.ssh.run(cmd_string)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py
new file mode 100644
index 0000000..572a238
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py
@@ -0,0 +1,506 @@
+#!/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 collections.abc
+import copy
+import fcntl
+import importlib
+import os
+import selenium
+import splinter
+import time
+from acts import logger
+
+BROWSER_WAIT_SHORT = 1
+BROWSER_WAIT_MED = 3
+BROWSER_WAIT_LONG = 30
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+def create(configs):
+    """Factory method for retail AP class.
+
+    Args:
+        configs: list of dicts containing ap settings. ap settings must contain
+        the following: brand, model, ip_address, username and password
+    """
+    SUPPORTED_APS = {
+        ('Netgear', 'R7000'): {
+            'name': 'NetgearR7000AP',
+            'package': 'netgear_r7000'
+        },
+        ('Netgear', 'R7000NA'): {
+            'name': 'NetgearR7000NAAP',
+            'package': 'netgear_r7000'
+        },
+        ('Netgear', 'R7500'): {
+            'name': 'NetgearR7500AP',
+            'package': 'netgear_r7500'
+        },
+        ('Netgear', 'R7500NA'): {
+        'name': 'NetgearR7500NAAP',
+        'package': 'netgear_r7500'
+        },
+        ('Netgear', 'R7800'): {
+            'name': 'NetgearR7800AP',
+            'package': 'netgear_r7800'
+        },
+        ('Netgear', 'R8000'): {
+            'name': 'NetgearR8000AP',
+            'package': 'netgear_r8000'
+        },
+        ('Netgear', 'RAX80'): {
+            'name': 'NetgearRAX80AP',
+            'package': 'netgear_rax80'
+        },
+        ('Netgear', 'RAX200'): {
+            'name': 'NetgearRAX200AP',
+            'package': 'netgear_rax200'
+        },
+        ('Netgear', 'RAX120'): {
+            'name': 'NetgearRAX120AP',
+            'package': 'netgear_rax120'
+        },
+        ('Google', 'Wifi'): {
+            'name': 'GoogleWifiAP',
+            'package': 'google_wifi'
+        },
+    }
+    objs = []
+    for config in configs:
+        ap_id = (config['brand'], config['model'])
+        if ap_id not in SUPPORTED_APS:
+            raise KeyError('Invalid retail AP brand and model combination.')
+        ap_class_dict = SUPPORTED_APS[ap_id]
+        ap_package = 'acts_contrib.test_utils.wifi.wifi_retail_ap.{}'.format(
+            ap_class_dict['package'])
+        ap_package = importlib.import_module(ap_package)
+        ap_class = getattr(ap_package, ap_class_dict['name'])
+        objs.append(ap_class(config))
+    return objs
+
+
+def destroy(objs):
+    for obj in objs:
+        obj.teardown()
+
+
+class BlockingBrowser(splinter.driver.webdriver.chrome.WebDriver):
+    """Class that implements a blocking browser session on top of selenium.
+
+    The class inherits from and builds upon splinter/selenium's webdriver class
+    and makes sure that only one such webdriver is active on a machine at any
+    single time. The class ensures single session operation using a lock file.
+    The class is to be used within context managers (e.g. with statements) to
+    ensure locks are always properly released.
+    """
+    def __init__(self, headless, timeout):
+        """Constructor for BlockingBrowser class.
+
+        Args:
+            headless: boolean to control visible/headless browser operation
+            timeout: maximum time allowed to launch browser
+        """
+        self.log = logger.create_tagged_trace_logger('ChromeDriver')
+        self.chrome_options = splinter.driver.webdriver.chrome.Options()
+        self.chrome_options.add_argument('--no-proxy-server')
+        self.chrome_options.add_argument('--no-sandbox')
+        self.chrome_options.add_argument('--allow-running-insecure-content')
+        self.chrome_options.add_argument('--ignore-certificate-errors')
+        self.chrome_capabilities = selenium.webdriver.common.desired_capabilities.DesiredCapabilities.CHROME.copy(
+        )
+        self.chrome_capabilities['acceptSslCerts'] = True
+        self.chrome_capabilities['acceptInsecureCerts'] = True
+        if headless:
+            self.chrome_options.add_argument('--headless')
+            self.chrome_options.add_argument('--disable-gpu')
+        self.lock_file_path = '/usr/local/bin/chromedriver'
+        self.timeout = timeout
+
+    def __enter__(self):
+        """Entry context manager for BlockingBrowser.
+
+        The enter context manager for BlockingBrowser attempts to lock the
+        browser file. If successful, it launches and returns a chromedriver
+        session. If an exception occurs while starting the browser, the lock
+        file is released.
+        """
+        self.lock_file = open(self.lock_file_path, 'r')
+        start_time = time.time()
+        while time.time() < start_time + self.timeout:
+            try:
+                fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            except BlockingIOError:
+                time.sleep(BROWSER_WAIT_SHORT)
+                continue
+            try:
+                self.driver = selenium.webdriver.Chrome(
+                    options=self.chrome_options,
+                    desired_capabilities=self.chrome_capabilities)
+                self.element_class = splinter.driver.webdriver.WebDriverElement
+                self._cookie_manager = splinter.driver.webdriver.cookie_manager.CookieManager(
+                    self.driver)
+                super(splinter.driver.webdriver.chrome.WebDriver,
+                      self).__init__(2)
+                return super(BlockingBrowser, self).__enter__()
+            except:
+                fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+                self.lock_file.close()
+                raise RuntimeError('Error starting browser. '
+                                   'Releasing lock file.')
+        raise TimeoutError('Could not start chrome browser in time.')
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        """Exit context manager for BlockingBrowser.
+
+        The exit context manager simply calls the parent class exit and
+        releases the lock file.
+        """
+        try:
+            super(BlockingBrowser, self).__exit__(exc_type, exc_value,
+                                                  traceback)
+        except:
+            raise RuntimeError('Failed to quit browser. Releasing lock file.')
+        finally:
+            fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+            self.lock_file.close()
+
+    def restart(self):
+        """Method to restart browser session without releasing lock file."""
+        self.quit()
+        self.__enter__()
+
+    def visit_persistent(self,
+                         url,
+                         page_load_timeout,
+                         num_tries,
+                         backup_url='about:blank',
+                         check_for_element=None):
+        """Method to visit webpages and retry upon failure.
+
+        The function visits a URL and checks that the resulting URL matches
+        the intended URL, i.e. no redirects have happened
+
+        Args:
+            url: the intended url
+            page_load_timeout: timeout for page visits
+            num_tries: number of tries before url is declared unreachable
+            backup_url: url to visit if first url is not reachable. This can be
+            used to simply refresh the browser and try again or to re-login to
+            the AP
+            check_for_element: element id to check for existence on page
+        """
+        self.driver.set_page_load_timeout(page_load_timeout)
+        for idx in range(num_tries):
+            try:
+                self.visit(url)
+            except:
+                self.restart()
+
+            page_reached = self.url.split('/')[-1] == url.split('/')[-1]
+            if check_for_element:
+                time.sleep(BROWSER_WAIT_MED)
+                element = self.find_by_id(check_for_element)
+                if not element:
+                    page_reached = 0
+            if page_reached:
+                break
+            else:
+                try:
+                    self.visit(backup_url)
+                except:
+                    self.restart()
+
+            if idx == num_tries - 1:
+                self.log.error('URL unreachable. Current URL: {}'.format(
+                    self.url))
+                raise RuntimeError('URL unreachable.')
+
+
+class WifiRetailAP(object):
+    """Base class implementation for retail ap.
+
+    Base class provides functions whose implementation is shared by all aps.
+    If some functions such as set_power not supported by ap, checks will raise
+    exceptions.
+    """
+    def __init__(self, ap_settings):
+        self.ap_settings = ap_settings.copy()
+        self.log = logger.create_tagged_trace_logger('AccessPoint|{}'.format(
+            self._get_control_ip_address()))
+        # Capabilities variable describing AP capabilities
+        self.capabilities = {
+            'interfaces': [],
+            'channels': {},
+            'modes': {},
+            'default_mode': None
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings.setdefault(interface, {})
+        # Lock AP
+        if self.ap_settings.get('lock_ap', 0):
+            self.lock_timeout = self.ap_settings.get('lock_timeout', 3600)
+            self._lock_ap()
+
+    def teardown(self):
+        """Function to perform destroy operations."""
+        self._unlock_ap()
+
+    def reset(self):
+        """Function that resets AP.
+
+        Function implementation is AP dependent and intended to perform any
+        necessary reset operations as part of controller destroy.
+        """
+        pass
+
+    def read_ap_settings(self):
+        """Function that reads current ap settings.
+
+        Function implementation is AP dependent and thus base class raises exception
+        if function not implemented in child class.
+        """
+        raise NotImplementedError
+
+    def validate_ap_settings(self):
+        """Function to validate ap settings.
+
+        This function compares the actual ap settings read from the web GUI
+        with the assumed settings saved in the AP object. When called after AP
+        configuration, this method helps ensure that our configuration was
+        successful.
+        Note: Calling this function updates the stored ap_settings
+
+        Raises:
+            ValueError: If read AP settings do not match stored settings.
+        """
+        assumed_ap_settings = copy.deepcopy(self.ap_settings)
+        actual_ap_settings = self.read_ap_settings()
+
+        if assumed_ap_settings != actual_ap_settings:
+            self.log.warning(
+                'Discrepancy in AP settings. Some settings may have been overwritten.'
+            )
+
+    def configure_ap(self, **config_flags):
+        """Function that configures ap based on values of ap_settings.
+
+        Function implementation is AP dependent and thus base class raises exception
+        if function not implemented in child class.
+
+        Args:
+            config_flags: optional configuration flags
+        """
+        raise NotImplementedError
+
+    def set_region(self, region):
+        """Function that sets AP region.
+
+        This function sets the region for the AP. Note that this may overwrite
+        channel and bandwidth settings in cases where the new region does not
+        support the current wireless configuration.
+
+        Args:
+            region: string indicating AP region
+        """
+        self.log.warning('Updating region may overwrite wireless settings.')
+        setting_to_update = {'region': region}
+        self.update_ap_settings(setting_to_update)
+
+    def set_radio_on_off(self, network, status):
+        """Function that turns the radio on or off.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            status: boolean indicating on or off (0: off, 1: on)
+        """
+        setting_to_update = {network: {'status': int(status)}}
+        self.update_ap_settings(setting_to_update)
+
+    def set_ssid(self, network, ssid):
+        """Function that sets network SSID.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            ssid: string containing ssid
+        """
+        setting_to_update = {network: {'ssid': str(ssid)}}
+        self.update_ap_settings(setting_to_update)
+
+    def set_channel(self, network, channel):
+        """Function that sets network channel.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: string or int containing channel
+        """
+        if channel not in self.capabilities['channels'][network]:
+            self.log.error('Ch{} is not supported on {} interface.'.format(
+                channel, network))
+        setting_to_update = {network: {'channel': str(channel)}}
+        self.update_ap_settings(setting_to_update)
+
+    def set_bandwidth(self, network, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        if 'bw' in bandwidth:
+            bandwidth = bandwidth.replace('bw',
+                                          self.capabilities['default_mode'])
+        if bandwidth not in self.capabilities['modes'][network]:
+            self.log.error('{} mode is not supported on {} interface.'.format(
+                bandwidth, network))
+        setting_to_update = {network: {'bandwidth': str(bandwidth)}}
+        self.update_ap_settings(setting_to_update)
+
+    def set_power(self, network, power):
+        """Function that sets network transmit power.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            power: string containing power level, e.g., 25%, 100%
+        """
+        if 'power' not in self.ap_settings[network].keys():
+            self.log.error(
+                'Cannot configure power on {} interface.'.format(network))
+        setting_to_update = {network: {'power': str(power)}}
+        self.update_ap_settings(setting_to_update)
+
+    def set_security(self, network, security_type, *password):
+        """Function that sets network security setting and password.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            security: string containing security setting, e.g., WPA2-PSK
+            password: optional argument containing password
+        """
+        if (len(password) == 1) and (type(password[0]) == str):
+            setting_to_update = {
+                network: {
+                    'security_type': str(security_type),
+                    'password': str(password[0])
+                }
+            }
+        else:
+            setting_to_update = {
+                network: {
+                    'security_type': str(security_type)
+                }
+            }
+        self.update_ap_settings(setting_to_update)
+
+    def set_rate(self):
+        """Function that configures rate used by AP.
+
+        Function implementation is not supported by most APs and thus base
+        class raises exception if function not implemented in child class.
+        """
+        raise NotImplementedError
+
+    def _update_settings_dict(self,
+                              settings,
+                              updates,
+                              updates_requested=False,
+                              status_toggle_flag=False):
+        new_settings = copy.deepcopy(settings)
+        for key, value in updates.items():
+            if key not in new_settings.keys():
+                raise KeyError('{} is an invalid settings key.'.format(key))
+            elif isinstance(value, collections.abc.Mapping):
+                new_settings[
+                    key], updates_requested, status_toggle_flag = self._update_settings_dict(
+                        new_settings.get(key, {}), value, updates_requested,
+                        status_toggle_flag)
+            elif new_settings[key] != value:
+                new_settings[key] = value
+                updates_requested = True
+                if 'status' in key:
+                    status_toggle_flag = True
+        return new_settings, updates_requested, status_toggle_flag
+
+    def update_ap_settings(self, dict_settings={}, **named_settings):
+        """Function to update settings of existing AP.
+
+        Function copies arguments into ap_settings and calls configure_retail_ap
+        to apply them.
+
+        Args:
+            *dict_settings accepts single dictionary of settings to update
+            **named_settings accepts named settings to update
+            Note: dict and named_settings cannot contain the same settings.
+        """
+        settings_to_update = dict(dict_settings, **named_settings)
+        if len(settings_to_update) != len(dict_settings) + len(named_settings):
+            raise KeyError('The following keys were passed twice: {}'.format(
+                (set(dict_settings.keys()).intersection(
+                    set(named_settings.keys())))))
+
+        self.ap_settings, updates_requested, status_toggle_flag = self._update_settings_dict(
+            self.ap_settings, settings_to_update)
+
+        if updates_requested:
+            self.configure_ap(status_toggled=status_toggle_flag)
+
+    def band_lookup_by_channel(self, channel):
+        """Function that gives band name by channel number.
+
+        Args:
+            channel: channel number to lookup
+        Returns:
+            band: name of band which this channel belongs to on this ap
+        """
+        for key, value in self.capabilities['channels'].items():
+            if channel in value:
+                return key
+        raise ValueError('Invalid channel passed in argument.')
+
+    def _get_control_ip_address(self):
+        """Function to get AP's Control Interface IP address."""
+        if 'ssh_config' in self.ap_settings.keys():
+            return self.ap_settings['ssh_config']['host']
+        else:
+            return self.ap_settings['ip_address']
+
+    def _lock_ap(self):
+        """Function to lock the ap while tests are running."""
+        self.lock_file_path = '/tmp/{}_{}_{}.lock'.format(
+            self.ap_settings['brand'], self.ap_settings['model'],
+            self._get_control_ip_address())
+        if not os.path.exists(self.lock_file_path):
+            with open(self.lock_file_path, 'w'):
+                pass
+        self.lock_file = open(self.lock_file_path, 'r')
+        start_time = time.time()
+        self.log.info('Trying to acquire AP lock.')
+        while time.time() < start_time + self.lock_timeout:
+            try:
+                fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            except BlockingIOError:
+                time.sleep(BROWSER_WAIT_SHORT)
+                continue
+            self.log.info('AP lock acquired.')
+            return
+        raise RuntimeError('Could not lock AP in time.')
+
+    def _unlock_ap(self):
+        """Function to unlock the AP when tests are done."""
+        self.log.info('Releasing AP lock.')
+        if hasattr(self, 'lock_file'):
+            fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+            self.lock_file.close()
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py
new file mode 100644
index 0000000..dd4ee9f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py
@@ -0,0 +1,273 @@
+#!/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 collections.abc
+from acts.controllers import access_point
+from acts.controllers.ap_lib import bridge_interface
+from acts.controllers.ap_lib import hostapd_security
+from acts.controllers.ap_lib import hostapd_ap_preset
+from acts_contrib.test_utils.wifi.wifi_retail_ap import WifiRetailAP
+
+
+class GoogleWifiAP(WifiRetailAP):
+    """ Class that implements Google Wifi AP.
+
+    This class is a work in progress
+    """
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        # Initialize AP
+        if self.ap_settings['2G']['status'] and self.ap_settings['5G_1'][
+                'status']:
+            raise ValueError('Error initializing Google Wifi AP. '
+                             'Only one interface can be enabled at a time.')
+
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [
+                    36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116,
+                    120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+                ]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40'],
+                '5G_1': ['VHT20', 'VHT40', 'VHT80']
+            },
+            'default_mode': 'VHT'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings.setdefault(interface, {})
+
+        self.BW_MODE_MAP = {
+            'legacy': 20,
+            'VHT20': 20,
+            'VHT40': 40,
+            'VHT80': 80
+        }
+        self.default_settings = {
+            'region': 'United States',
+            'brand': 'Google',
+            'model': 'Wifi',
+            'hostapd_profile': 'whirlwind',
+            '2G': {
+                'status': 0,
+                'ssid': 'GoogleWifi_2G',
+                'channel': 11,
+                'bandwidth': 'VHT20',
+                'power': 'auto',
+                'mode': None,
+                'num_streams': None,
+                'rate': 'auto',
+                'short_gi': 0,
+                'security_type': 'Open',
+                'password': 'password',
+                'subnet': '192.168.1.0/24'
+            },
+            '5G_1': {
+                'status': 0,
+                'ssid': 'GoogleWifi_2G',
+                'channel': 11,
+                'bandwidth': 'VHT20',
+                'power': 'auto',
+                'mode': None,
+                'num_streams': None,
+                'rate': 'auto',
+                'short_gi': 0,
+                'security_type': 'Open',
+                'password': 'password',
+                'subnet': '192.168.9.0/24'
+            }
+        }
+
+        for interface in self.capabilities['interfaces']:
+            for setting in self.default_settings[interface].keys():
+                if setting not in self.ap_settings[interface]:
+                    self.log.debug(
+                        '{0} {1} not found during init. Setting {1} = {2}'.
+                        format(interface, setting,
+                               self.default_settings[interface][setting]))
+                    self.ap_settings[interface][
+                        setting] = self.default_settings[interface][setting]
+        init_settings = self.ap_settings.copy()
+        init_settings['ap_subnet'] = {
+            '2g': self.ap_settings['2G']['subnet'],
+            '5g': self.ap_settings['5G_1']['subnet']
+        }
+        self.access_point = access_point.AccessPoint(init_settings)
+        self.configure_ap()
+
+    def read_ap_settings(self):
+        """Function that reads current ap settings."""
+        return self.ap_settings.copy()
+
+    def update_ap_settings(self, dict_settings={}, **named_settings):
+        """Function to update settings of existing AP.
+
+        Function copies arguments into ap_settings and calls configure_ap
+        to apply them.
+
+        Args:
+            dict_settings: single dictionary of settings to update
+            **named_settings: named settings to update
+            Note: dict and named_settings cannot contain the same settings.
+        """
+        settings_to_update = dict(dict_settings, **named_settings)
+        if len(settings_to_update) != len(dict_settings) + len(named_settings):
+            raise KeyError('The following keys were passed twice: {}'.format(
+                (set(dict_settings.keys()).intersection(
+                    set(named_settings.keys())))))
+
+        updating_2G = '2G' in settings_to_update.keys()
+        updating_5G_1 = '5G_1' in settings_to_update.keys()
+        if updating_2G and updating_5G_1:
+            raise ValueError(
+                'Error updating Google WiFi AP. '
+                'One interface can be activated and updated at a time')
+        elif updating_2G:
+            # If updating an interface and not explicitly setting its status,
+            # it is assumed that the interface is to be ENABLED and updated
+            if 'status' not in settings_to_update['2G']:
+                settings_to_update['2G']['status'] = 1
+                settings_to_update['5G_1'] = {'status': 0}
+        elif updating_5G_1:
+            if 'status' not in settings_to_update['5G_1']:
+                settings_to_update['2G'] = {'status': 0}
+                settings_to_update['5G_1']['status'] = 1
+        self.ap_settings, updates_requested, status_toggle_flag = self._update_settings_dict(
+            self.ap_settings, settings_to_update)
+        if updates_requested:
+            self.configure_ap()
+
+    def configure_ap(self):
+        """Function to configure Google Wifi."""
+        self.log.info('Stopping Google Wifi interfaces.')
+        print(self.ap_settings)
+        self.access_point.stop_all_aps()
+
+        if self.ap_settings['2G']['status'] == 1:
+            interface = '2G'
+            self.log.info('Bringing up 2.4 GHz interface.')
+        elif self.ap_settings['5G_1']['status'] == 1:
+            interface = '5G_1'
+            self.log.info('Bringing up 5 GHz interface.')
+        else:
+            return
+
+        bss_settings = []
+        ssid = self.ap_settings[interface]['ssid']
+        security_mode = self.ap_settings[interface]['security_type'].lower()
+        if 'wpa' in security_mode:
+            password = self.ap_settings[interface]['password']
+            security = hostapd_security.Security(security_mode=security_mode,
+                                                 password=password)
+        else:
+            security = hostapd_security.Security(security_mode=None,
+                                                 password=None)
+        channel = int(self.ap_settings[interface]['channel'])
+        bandwidth = self.BW_MODE_MAP[self.ap_settings[interface]['bandwidth']]
+        config = hostapd_ap_preset.create_ap_preset(
+            channel=channel,
+            ssid=ssid,
+            security=security,
+            bss_settings=bss_settings,
+            vht_bandwidth=bandwidth,
+            profile_name=self.ap_settings['hostapd_profile'],
+            iface_wlan_2g=self.access_point.wlan_2g,
+            iface_wlan_5g=self.access_point.wlan_5g)
+        config_bridge = self.access_point.generate_bridge_configs(channel)
+        brconfigs = bridge_interface.BridgeInterfaceConfigs(
+            config_bridge[0], 'lan0', config_bridge[2])
+        self.access_point.bridge.startup(brconfigs)
+        self.access_point.start_ap(config)
+        self.set_power(interface, self.ap_settings[interface]['power'])
+        self.set_rate(interface,
+                      mode=self.ap_settings[interface]['mode'],
+                      num_streams=self.ap_settings[interface]['num_streams'],
+                      rate=self.ap_settings[interface]['rate'],
+                      short_gi=self.ap_settings[interface]['short_gi'])
+        self.log.info('AP started on channel {} with SSID {}'.format(
+            channel, ssid))
+
+    def set_power(self, interface, power):
+        """Function that sets interface transmit power.
+
+        Args:
+            interface: string containing interface identifier (2G, 5G_1)
+            power: power level in dBm
+        """
+        if power == 'auto':
+            power_string = 'auto'
+        else:
+            if not float(power).is_integer():
+                self.log.info(
+                    'Power in dBm must be an integer. Setting to {}'.format(
+                        int(power)))
+            power = int(power)
+            power_string = 'fixed {}'.format(int(power) * 100)
+
+        if '2G' in interface:
+            interface_long = self.access_point.wlan_2g
+            self.ap_settings[interface]['power'] = power
+        elif '5G_1' in interface:
+            interface_long = self.access_point.wlan_5g
+            self.ap_settings[interface]['power'] = power
+        self.access_point.ssh.run('iw dev {} set txpower {}'.format(
+            interface_long, power_string))
+
+    def set_rate(self,
+                 interface,
+                 mode=None,
+                 num_streams=None,
+                 rate='auto',
+                 short_gi=0):
+        """Function that sets rate.
+
+        Args:
+            interface: string containing interface identifier (2G, 5G_1)
+            mode: string indicating the WiFi standard to use
+            num_streams: number of MIMO streams. used only for VHT
+            rate: data rate of MCS index to use
+            short_gi: boolean controlling the use of short guard interval
+        """
+        if '2G' in interface:
+            interface_long = self.access_point.wlan_2g
+            interface_short = '2.4'
+        elif '5G_1' in interface:
+            interface_long = self.access_point.wlan_5g
+            interface_short = '5'
+        self.ap_settings[interface]['mode'] = mode
+        self.ap_settings[interface]['num_streams'] = num_streams
+        self.ap_settings[interface]['rate'] = rate
+        self.ap_settings[interface]['short_gi'] = short_gi
+
+        if rate == 'auto':
+            cmd_string = 'iw dev {0} set bitrates'.format(interface_long)
+        elif 'legacy' in mode.lower():
+            cmd_string = 'iw dev {0} set bitrates legacy-{1} {2} ht-mcs-{1} vht-mcs-{1}'.format(
+                interface_long, interface_short, rate)
+        elif 'vht' in mode.lower():
+            cmd_string = 'iw dev {0} set bitrates legacy-{1} ht-mcs-{1} vht-mcs-{1} {2}:{3}'.format(
+                interface_long, interface_short, num_streams, rate)
+            if short_gi:
+                cmd_string = cmd_string + ' sgi-{}'.format(interface_short)
+        elif 'ht' in mode.lower():
+            cmd_string = 'iw dev {0} set bitrates legacy-{1} ht-mcs-{1} {2} vht-mcs-{1}'.format(
+                interface_long, interface_short, rate)
+            if short_gi:
+                cmd_string = cmd_string + ' sgi-{}'.format(interface_short)
+        self.access_point.ssh.run(cmd_string)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py
new file mode 100644
index 0000000..e7f4e83
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py
@@ -0,0 +1,282 @@
+#!/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
+from acts_contrib.test_utils.wifi.wifi_retail_ap import WifiRetailAP
+from acts_contrib.test_utils.wifi.wifi_retail_ap import BlockingBrowser
+
+BROWSER_WAIT_SHORT = 1
+BROWSER_WAIT_MED = 3
+BROWSER_WAIT_LONG = 30
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+class NetgearR7000AP(WifiRetailAP):
+    """Class that implements Netgear R7000 AP."""
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Read and update AP settings
+        self.read_ap_settings()
+        self.update_ap_settings(ap_settings)
+
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        self.config_page = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_wireless_dual_band_r10.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_nologin = (
+            '{protocol}://{ip_address}:{port}/'
+            'WLG_wireless_dual_band_r10.htm').format(
+                protocol=self.ap_settings['protocol'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_advanced = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_adv_dual_band2.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [
+                    36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116,
+                    120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+                ]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40'],
+                '5G_1': ['VHT20', 'VHT40', 'VHT80']
+            },
+            'default_mode': 'VHT'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {}
+
+        self.region_map = {
+            '1': 'Africa',
+            '2': 'Asia',
+            '3': 'Australia',
+            '4': 'Canada',
+            '5': 'Europe',
+            '6': 'Israel',
+            '7': 'Japan',
+            '8': 'Korea',
+            '9': 'Mexico',
+            '10': 'South America',
+            '11': 'United States',
+            '12': 'Middle East(Algeria/Syria/Yemen)',
+            '14': 'Russia',
+            '16': 'China',
+            '17': 'India',
+            '18': 'Malaysia',
+            '19': 'Middle East(Iran/Labanon/Qatar)',
+            '20': 'Middle East(Turkey/Egypt/Tunisia/Kuwait)',
+            '21': 'Middle East(Saudi Arabia)',
+            '22': 'Middle East(United Arab Emirates)',
+            '23': 'Singapore',
+            '24': 'Taiwan'
+        }
+        self.config_page_fields = {
+            'region': 'WRegion',
+            ('2G', 'status'): 'enable_ap',
+            ('5G_1', 'status'): 'enable_ap_an',
+            ('2G', 'ssid'): 'ssid',
+            ('5G_1', 'ssid'): 'ssid_an',
+            ('2G', 'channel'): 'w_channel',
+            ('5G_1', 'channel'): 'w_channel_an',
+            ('2G', 'bandwidth'): 'opmode',
+            ('5G_1', 'bandwidth'): 'opmode_an',
+            ('2G', 'power'): 'enable_tpc',
+            ('5G_1', 'power'): 'enable_tpc_an',
+            ('2G', 'security_type'): 'security_type',
+            ('5G_1', 'security_type'): 'security_type_an',
+            ('2G', 'password'): 'passphrase',
+            ('5G_1', 'password'): 'passphrase_an'
+        }
+        self.bw_mode_values = {
+            'g and b': '11g',
+            '145Mbps': 'VHT20',
+            '300Mbps': 'VHT40',
+            'HT80': 'VHT80'
+        }
+        self.power_mode_values = {
+            '1': '100%',
+            '2': '75%',
+            '3': '50%',
+            '4': '25%'
+        }
+        self.bw_mode_text = {
+            '11g': 'Up to 54 Mbps',
+            'VHT20': 'Up to 289 Mbps',
+            'VHT40': 'Up to 600 Mbps',
+            'VHT80': 'Up to 1300 Mbps'
+        }
+
+    def read_ap_settings(self):
+        """Function to read ap settings."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+
+            for key, value in self.config_page_fields.items():
+                if 'status' in key:
+                    browser.visit_persistent(self.config_page_advanced,
+                                             BROWSER_WAIT_MED, 10)
+                    config_item = browser.find_by_name(value)
+                    self.ap_settings[key[0]][key[1]] = int(
+                        config_item.first.checked)
+                    browser.visit_persistent(self.config_page,
+                                             BROWSER_WAIT_MED, 10)
+                else:
+                    config_item = browser.find_by_name(value)
+                    if 'bandwidth' in key:
+                        self.ap_settings[key[0]][key[1]] = self.bw_mode_values[
+                            config_item.first.value]
+                    elif 'power' in key:
+                        self.ap_settings[key[0]][
+                            key[1]] = self.power_mode_values[
+                                config_item.first.value]
+                    elif 'region' in key:
+                        self.ap_settings['region'] = self.region_map[
+                            config_item.first.value]
+                    elif 'security_type' in key:
+                        for item in config_item:
+                            if item.checked:
+                                self.ap_settings[key[0]][key[1]] = item.value
+                    else:
+                        config_item = browser.find_by_name(value)
+                        self.ap_settings[key[0]][
+                            key[1]] = config_item.first.value
+        return self.ap_settings.copy()
+
+    def configure_ap(self, **config_flags):
+        """Function to configure ap wireless settings."""
+        # Turn radios on or off
+        if config_flags['status_toggled']:
+            self.configure_radio_on_off()
+        # Configure radios
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_nologin,
+                                     BROWSER_WAIT_MED, 10, self.config_page)
+
+            # Update region, and power/bandwidth for each network
+            config_item = browser.find_by_name(
+                self.config_page_fields['region']).first
+            config_item.select_by_text(self.ap_settings['region'])
+            for key, value in self.config_page_fields.items():
+                if 'power' in key:
+                    config_item = browser.find_by_name(value).first
+                    config_item.select_by_text(
+                        self.ap_settings[key[0]][key[1]])
+                elif 'bandwidth' in key:
+                    config_item = browser.find_by_name(value).first
+                    try:
+                        config_item.select_by_text(self.bw_mode_text[
+                            self.ap_settings[key[0]][key[1]]])
+                    except AttributeError:
+                        self.log.warning(
+                            'Cannot select bandwidth. Keeping AP default.')
+
+            # Update security settings (passwords updated only if applicable)
+            for key, value in self.config_page_fields.items():
+                if 'security_type' in key:
+                    browser.choose(value, self.ap_settings[key[0]][key[1]])
+                    if self.ap_settings[key[0]][key[1]] == 'WPA2-PSK':
+                        config_item = browser.find_by_name(
+                            self.config_page_fields[(key[0],
+                                                     'password')]).first
+                        config_item.fill(self.ap_settings[key[0]]['password'])
+
+            # Update SSID and channel for each network
+            # NOTE: Update ordering done as such as workaround for R8000
+            # wherein channel and SSID get overwritten when some other
+            # variables are changed. However, region does have to be set before
+            # channel in all cases.
+            for key, value in self.config_page_fields.items():
+                if 'ssid' in key:
+                    config_item = browser.find_by_name(value).first
+                    config_item.fill(self.ap_settings[key[0]][key[1]])
+                elif 'channel' in key:
+                    config_item = browser.find_by_name(value).first
+                    try:
+                        config_item.select(self.ap_settings[key[0]][key[1]])
+                        time.sleep(BROWSER_WAIT_SHORT)
+                    except AttributeError:
+                        self.log.warning(
+                            'Cannot select channel. Keeping AP default.')
+                    try:
+                        alert = browser.get_alert()
+                        alert.accept()
+                    except:
+                        pass
+
+            time.sleep(BROWSER_WAIT_SHORT)
+            browser.find_by_name('Apply').first.click()
+            time.sleep(BROWSER_WAIT_SHORT)
+            try:
+                alert = browser.get_alert()
+                alert.accept()
+                time.sleep(BROWSER_WAIT_SHORT)
+            except:
+                time.sleep(BROWSER_WAIT_SHORT)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+
+    def configure_radio_on_off(self):
+        """Helper configuration function to turn radios on/off."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED, 10)
+
+            # Turn radios on or off
+            for key, value in self.config_page_fields.items():
+                if 'status' in key:
+                    config_item = browser.find_by_name(value).first
+                    if self.ap_settings[key[0]][key[1]]:
+                        config_item.check()
+                    else:
+                        config_item.uncheck()
+
+            time.sleep(BROWSER_WAIT_SHORT)
+            browser.find_by_name('Apply').first.click()
+            time.sleep(BROWSER_WAIT_EXTRA_LONG)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+
+
+class NetgearR7000NAAP(NetgearR7000AP):
+    """Class that implements Netgear R7000 NA AP."""
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        super.init_gui_data()
+        self.region_map['11'] = 'North America'
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py
new file mode 100644
index 0000000..06329b6
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py
@@ -0,0 +1,335 @@
+#!/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 selenium
+import time
+from acts_contrib.test_utils.wifi.wifi_retail_ap import WifiRetailAP
+from acts_contrib.test_utils.wifi.wifi_retail_ap import BlockingBrowser
+
+BROWSER_WAIT_SHORT = 1
+BROWSER_WAIT_MED = 3
+BROWSER_WAIT_LONG = 30
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+class NetgearR7500AP(WifiRetailAP):
+    """Class that implements Netgear R7500 AP."""
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Read and update AP settings
+        self.read_ap_settings()
+        self.update_ap_settings(ap_settings)
+
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        self.config_page = ('{protocol}://{username}:{password}@'
+                            '{ip_address}:{port}/index.htm').format(
+                                protocol=self.ap_settings['protocol'],
+                                username=self.ap_settings['admin_username'],
+                                password=self.ap_settings['admin_password'],
+                                ip_address=self.ap_settings['ip_address'],
+                                port=self.ap_settings['port'])
+        self.config_page_advanced = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/adv_index.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [
+                    36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116,
+                    120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+                ]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40'],
+                '5G_1': ['VHT20', 'VHT40', 'VHT80']
+            },
+            'default_mode': 'VHT'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {}
+
+        self.config_page_fields = {
+            'region': 'WRegion',
+            ('2G', 'status'): 'enable_ap',
+            ('5G_1', 'status'): 'enable_ap_an',
+            ('2G', 'ssid'): 'ssid',
+            ('5G_1', 'ssid'): 'ssid_an',
+            ('2G', 'channel'): 'w_channel',
+            ('5G_1', 'channel'): 'w_channel_an',
+            ('2G', 'bandwidth'): 'opmode',
+            ('5G_1', 'bandwidth'): 'opmode_an',
+            ('2G', 'security_type'): 'security_type',
+            ('5G_1', 'security_type'): 'security_type_an',
+            ('2G', 'password'): 'passphrase',
+            ('5G_1', 'password'): 'passphrase_an'
+        }
+        self.region_map = {
+            '0': 'Africa',
+            '1': 'Asia',
+            '2': 'Australia',
+            '3': 'Canada',
+            '4': 'Europe',
+            '5': 'Israel',
+            '6': 'Japan',
+            '7': 'Korea',
+            '8': 'Mexico',
+            '9': 'South America',
+            '10': 'United States',
+            '11': 'China',
+            '12': 'India',
+            '13': 'Malaysia',
+            '14': 'Middle East(Algeria/Syria/Yemen)',
+            '15': 'Middle East(Iran/Labanon/Qatar)',
+            '16': 'Middle East(Turkey/Egypt/Tunisia/Kuwait)',
+            '17': 'Middle East(Saudi Arabia)',
+            '18': 'Middle East(United Arab Emirates)',
+            '19': 'Russia',
+            '20': 'Singapore',
+            '21': 'Taiwan'
+        }
+        self.bw_mode_text = {
+            '2G': {
+                '11g': 'Up to 54 Mbps',
+                'VHT20': 'Up to 289 Mbps',
+                'VHT40': 'Up to 600 Mbps'
+            },
+            '5G_1': {
+                'VHT20': 'Up to 347 Mbps',
+                'VHT40': 'Up to 800 Mbps',
+                'VHT80': 'Up to 1733 Mbps'
+            }
+        }
+        self.bw_mode_values = {
+            '1': '11g',
+            '2': 'VHT20',
+            '3': 'VHT40',
+            '7': 'VHT20',
+            '8': 'VHT40',
+            '9': 'VHT80'
+        }
+        self.security_mode_values = {
+            '2G': {
+                'Disable': 'security_disable',
+                'WPA2-PSK': 'security_wpa2'
+            },
+            '5G_1': {
+                'Disable': 'security_an_disable',
+                'WPA2-PSK': 'security_an_wpa2'
+            }
+        }
+
+    def read_ap_settings(self):
+        """Function to read ap wireless settings."""
+        # Get radio status (on/off)
+        self.read_radio_on_off()
+        # Get radio configuration. Note that if both radios are off, the below
+        # code will result in an error
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element='wireless')
+            wireless_button = browser.find_by_id('wireless').first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe('formframe') as iframe:
+                for key, value in self.config_page_fields.items():
+                    if 'bandwidth' in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings[key[0]][key[1]] = self.bw_mode_values[
+                            config_item.value]
+                    elif 'region' in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings['region'] = self.region_map[
+                            config_item.value]
+                    elif 'password' in key:
+                        try:
+                            config_item = iframe.find_by_name(value).first
+                            self.ap_settings[key[0]][
+                                key[1]] = config_item.value
+                            self.ap_settings[
+                                key[0]]['security_type'] = 'WPA2-PSK'
+                        except:
+                            self.ap_settings[key[0]][
+                                key[1]] = 'defaultpassword'
+                            self.ap_settings[
+                                key[0]]['security_type'] = 'Disable'
+                    elif ('channel' in key) or ('ssid' in key):
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings[key[0]][key[1]] = config_item.value
+                    else:
+                        pass
+        return self.ap_settings.copy()
+
+    def configure_ap(self, **config_flags):
+        """Function to configure ap wireless settings."""
+        # Turn radios on or off
+        if config_flags['status_toggled']:
+            self.configure_radio_on_off()
+        # Configure radios
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element='wireless')
+            wireless_button = browser.find_by_id('wireless').first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe('formframe') as iframe:
+                # Update AP region. Must be done before channel setting
+                config_item = iframe.find_by_name(
+                    self.config_page_fields['region']).first
+                config_item.select_by_text(self.ap_settings['region'])
+                # Update wireless settings for each network
+                for key, value in self.config_page_fields.items():
+                    if 'ssid' in key:
+                        config_item = iframe.find_by_name(value).first
+                        config_item.fill(self.ap_settings[key[0]][key[1]])
+                    elif 'channel' in key:
+                        channel = self.ap_settings[key[0]][key[1]]
+                        if int(channel) < 10:
+                            channel_string = '0' + str(channel)
+                        elif int(channel) > 48 and int(channel) < 149:
+                            channel_string = str(channel) + 'DFS'
+                        else:
+                            channel_string = str(channel)
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(channel_string)
+                        except AttributeError:
+                            self.log.warning(
+                                'Cannot select channel. Keeping AP default.')
+                    elif 'bandwidth' in key:
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(
+                                str(self.bw_mode_text[key[0]][self.ap_settings[
+                                    key[0]][key[1]]]))
+                        except AttributeError:
+                            self.log.warning(
+                                'Cannot select bandwidth. Keeping AP default.')
+                # Update passwords for WPA2-PSK protected networks
+                # (Must be done after security type is selected)
+                for key, value in self.config_page_fields.items():
+                    if 'security_type' in key:
+                        security_option = browser.driver.find_element_by_id(
+                            self.security_mode_values[key[0]][self.ap_settings[
+                                key[0]][key[1]]])
+                        action = selenium.webdriver.common.action_chains.ActionChains(
+                            browser.driver)
+                        action.move_to_element(
+                            security_option).click().perform()
+                        if self.ap_settings[key[0]][key[1]] == 'WPA2-PSK':
+                            config_item = iframe.find_by_name(
+                                self.config_page_fields[(key[0],
+                                                         'password')]).first
+                            config_item.fill(
+                                self.ap_settings[key[0]]['password'])
+
+                apply_button = iframe.find_by_name('Apply')
+                apply_button[0].click()
+                time.sleep(BROWSER_WAIT_SHORT)
+                try:
+                    alert = browser.get_alert()
+                    alert.accept()
+                except:
+                    pass
+                time.sleep(BROWSER_WAIT_SHORT)
+                try:
+                    alert = browser.get_alert()
+                    alert.accept()
+                except:
+                    pass
+                time.sleep(BROWSER_WAIT_SHORT)
+            time.sleep(BROWSER_WAIT_EXTRA_LONG)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+
+    def configure_radio_on_off(self):
+        """Helper configuration function to turn radios on/off."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element='advanced_bt')
+            advanced_button = browser.find_by_id('advanced_bt').first
+            advanced_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+            wireless_button = browser.find_by_id('wladv').first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe('formframe') as iframe:
+                # Turn radios on or off
+                for key, value in self.config_page_fields.items():
+                    if 'status' in key:
+                        config_item = iframe.find_by_name(value).first
+                        if self.ap_settings[key[0]][key[1]]:
+                            config_item.check()
+                        else:
+                            config_item.uncheck()
+
+                time.sleep(BROWSER_WAIT_SHORT)
+                browser.find_by_name('Apply').first.click()
+                time.sleep(BROWSER_WAIT_EXTRA_LONG)
+                browser.visit_persistent(self.config_page,
+                                         BROWSER_WAIT_EXTRA_LONG, 10)
+
+    def read_radio_on_off(self):
+        """Helper configuration function to read radio status."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element='advanced_bt')
+            advanced_button = browser.find_by_id('advanced_bt').first
+            advanced_button.click()
+            time.sleep(BROWSER_WAIT_SHORT)
+            wireless_button = browser.find_by_id('wladv').first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe('formframe') as iframe:
+                # Turn radios on or off
+                for key, value in self.config_page_fields.items():
+                    if 'status' in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings[key[0]][key[1]] = int(
+                            config_item.checked)
+
+
+class NetgearR7500NAAP(NetgearR7500AP):
+    """Class that implements Netgear R7500 NA AP."""
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        super().init_gui_data()
+        self.region_map['10'] = 'North America'
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7800.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7800.py
new file mode 100644
index 0000000..e06a3e3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7800.py
@@ -0,0 +1,29 @@
+#!/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.
+
+from acts_contrib.test_utils.wifi.wifi_retail_ap import NetgearR7500AP
+
+
+class NetgearR7800AP(NetgearR7500AP):
+    """Class that implements Netgear R7800 AP.
+
+    Since most of the class' implementation is shared with the R7500, this
+    class inherits from NetgearR7500AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        super().init_gui_data()
+        # Overwrite minor differences from R7500 AP
+        self.bw_mode_text_2g['VHT20'] = 'Up to 347 Mbps'
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r8000.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r8000.py
new file mode 100644
index 0000000..0b9cbf9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r8000.py
@@ -0,0 +1,88 @@
+#!/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.
+
+from acts_contrib.test_utils.wifi.wifi_retail_ap import NetgearR7000AP
+
+
+class NetgearR8000AP(NetgearR7000AP):
+    """Class that implements Netgear R8000 AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        super().init_gui_data()
+        # Overwrite minor differences from R7000 AP
+        self.config_page = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_wireless_dual_band_r8000.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_nologin = (
+            '{protocol}://{ip_address}:{port}/'
+            'WLG_wireless_dual_band_r8000.htm').format(
+                protocol=self.ap_settings['protocol'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_advanced = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_adv_dual_band2_r8000.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1', '5G_2'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [36, 40, 44, 48],
+                '5G_2': [149, 153, 157, 161, 165]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40'],
+                '5G_1': ['VHT20', 'VHT40', 'VHT80'],
+                '5G_2': ['VHT20', 'VHT40', 'VHT80']
+            },
+            'default_mode': 'VHT'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {}
+
+        self.config_page_fields = {
+            'region': 'WRegion',
+            ('2G', 'status'): 'enable_ap',
+            ('5G_1', 'status'): 'enable_ap_an',
+            ('5G_2', 'status'): 'enable_ap_an_2',
+            ('2G', 'ssid'): 'ssid',
+            ('5G_1', 'ssid'): 'ssid_an',
+            ('5G_2', 'ssid'): 'ssid_an_2',
+            ('2G', 'channel'): 'w_channel',
+            ('5G_1', 'channel'): 'w_channel_an',
+            ('5G_2', 'channel'): 'w_channel_an_2',
+            ('2G', 'bandwidth'): 'opmode',
+            ('5G_1', 'bandwidth'): 'opmode_an',
+            ('5G_2', 'bandwidth'): 'opmode_an_2',
+            ('2G', 'security_type'): 'security_type',
+            ('5G_1', 'security_type'): 'security_type_an',
+            ('5G_2', 'security_type'): 'security_type_an_2',
+            ('2G', 'password'): 'passphrase',
+            ('5G_1', 'password'): 'passphrase_an',
+            ('5G_2', 'password'): 'passphrase_an_2'
+        }
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax120.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax120.py
new file mode 100644
index 0000000..e718ebd
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax120.py
@@ -0,0 +1,338 @@
+#!/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 selenium
+import time
+from acts_contrib.test_utils.wifi.wifi_retail_ap.netgear_r7500 import NetgearR7500AP
+from acts_contrib.test_utils.wifi.wifi_retail_ap import BlockingBrowser
+
+BROWSER_WAIT_SHORT = 1
+BROWSER_WAIT_MED = 3
+BROWSER_WAIT_LONG = 30
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+class NetgearRAX120AP(NetgearR7500AP):
+    """Class that implements Netgear RAX120 AP.
+
+    Since most of the class' implementation is shared with the R7500, this
+    class inherits from NetgearR7500AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        self.config_page = ('{protocol}://{username}:{password}@'
+                            '{ip_address}:{port}/index.htm').format(
+                                protocol=self.ap_settings['protocol'],
+                                username=self.ap_settings['admin_username'],
+                                password=self.ap_settings['admin_password'],
+                                ip_address=self.ap_settings['ip_address'],
+                                port=self.ap_settings['port'])
+        self.config_page_advanced = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/adv_index.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [
+                    36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116,
+                    120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+                ]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40', 'HE20', 'HE40'],
+                '5G_1': [
+                    'VHT20', 'VHT40', 'VHT80', 'VHT160', 'HE20', 'HE40',
+                    'HE80', 'HE160'
+                ]
+            },
+            'default_mode': 'HE'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {}
+
+        self.config_page_fields = {
+            'region': 'WRegion',
+            'enable_ax': 'enable_ax_chec',
+            ('2G', 'status'): 'enable_ap',
+            ('5G_1', 'status'): 'enable_ap_an',
+            ('2G', 'ssid'): 'ssid',
+            ('5G_1', 'ssid'): 'ssid_an',
+            ('2G', 'channel'): 'w_channel',
+            ('5G_1', 'channel'): 'w_channel_an',
+            ('2G', 'bandwidth'): 'opmode',
+            ('5G_1', 'bandwidth'): 'opmode_an',
+            ('2G', 'security_type'): 'security_type',
+            ('5G_1', 'security_type'): 'security_type_an',
+            ('2G', 'password'): 'passphrase',
+            ('5G_1', 'password'): 'passphrase_an'
+        }
+        self.region_map = {
+            '0': 'Africa',
+            '1': 'Asia',
+            '2': 'Australia',
+            '3': 'Canada',
+            '4': 'Europe',
+            '5': 'Israel',
+            '6': 'Japan',
+            '7': 'Korea',
+            '8': 'Mexico',
+            '9': 'South America',
+            '10': 'United States',
+            '11': 'China',
+            '12': 'India',
+            '13': 'Malaysia',
+            '14': 'Middle East(Algeria/Syria/Yemen)',
+            '15': 'Middle East(Iran/Labanon/Qatar)',
+            '16': 'Middle East(Egypt/Tunisia/Kuwait)',
+            '17': 'Middle East(Turkey)',
+            '18': 'Middle East(Saudi Arabia/United Arab Emirates)',
+            '19': 'Russia',
+            '20': 'Singapore',
+            '21': 'Taiwan',
+        }
+        self.bw_mode_text = {
+            '2G': {
+                '11g': 'Up to 54 Mbps (11g)',
+                'HE20': 'Up to 573.5 Mbps (11ax, HT20, 1024-QAM)',
+                'HE40': 'Up to 1147 Mbps (11ax, HT40, 1024-QAM)',
+                'VHT20': 'Up to 481 Mbps (11ng, HT20, 1024-QAM)',
+                'VHT40': 'Up to 1000 Mbps (11ng, HT40, 1024-QAM)'
+            },
+            '5G_1': {
+                'HE20': 'Up to 1147 Mbps (11ax, HT20, 1024-QAM)',
+                'HE40': 'Up to 2294 Mbps (11ax, HT40, 1024-QAM)',
+                'HE80': 'Up to 4803 Mbps (11ax, HT80, 1024-QAM)',
+                'HE160': 'Up to 4803 Mbps (11ax, HT160, 1024-QAM)',
+                'VHT20': 'Up to 962 Mbps (11ac, HT20, 1024-QAM)',
+                'VHT40': 'Up to 2000 Mbps (11ac, HT40, 1024-QAM)',
+                'VHT80': 'Up to 4333 Mbps (11ac, HT80, 1024-QAM)',
+                'VHT160': 'Up to 4333 Mbps (11ac, HT160, 1024-QAM)'
+            }
+        }
+        self.bw_mode_values = {
+            # first key is a boolean indicating if 11ax is enabled
+            0: {
+                '1': '11g',
+                '2': 'VHT20',
+                '3': 'VHT40',
+                '7': 'VHT20',
+                '8': 'VHT40',
+                '9': 'VHT80',
+                '10': 'VHT160'
+            },
+            1: {
+                '1': '11g',
+                '2': 'HE20',
+                '3': 'HE40',
+                '7': 'HE20',
+                '8': 'HE40',
+                '9': 'HE80',
+                '10': 'HE160'
+            }
+        }
+        self.security_mode_values = {
+            '2G': {
+                'Disable': 'security_disable',
+                'WPA2-PSK': 'security_wpa2'
+            },
+            '5G_1': {
+                'Disable': 'security_an_disable',
+                'WPA2-PSK': 'security_an_wpa2'
+            }
+        }
+
+    def set_bandwidth(self, network, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        if 'bw' in bandwidth:
+            bandwidth = bandwidth.replace('bw',
+                                          self.capabilities['default_mode'])
+        if bandwidth not in self.capabilities['modes'][network]:
+            self.log.error('{} mode is not supported on {} interface.'.format(
+                bandwidth, network))
+        setting_to_update = {network: {'bandwidth': str(bandwidth)}}
+        setting_to_update['enable_ax'] = int('HE' in bandwidth)
+        # Check if other interfaces need to be changed too
+        requested_mode = 'HE' if 'HE' in bandwidth else 'VHT'
+        other_network = '2G' if '5G_1' in network else '5G_1'
+        other_mode = 'HE' if 'HE' in self.ap_settings[other_network][
+            'bandwidth'] else 'VHT'
+        other_bw = ''.join([
+            x for x in self.ap_settings[other_network]['bandwidth']
+            if x.isdigit()
+        ])
+        if other_mode != requested_mode:
+            updated_mode = '{}{}'.format(requested_mode, other_bw)
+            self.log.warning('All networks must be VHT or HE. '
+                             'Updating {} to {}'.format(
+                                 other_network, updated_mode))
+            setting_to_update.setdefault(other_network, {})
+            setting_to_update[other_network]['bandwidth'] = updated_mode
+
+        self.update_ap_settings(setting_to_update)
+
+    def read_ap_settings(self):
+        """Function to read ap wireless settings."""
+        # Get radio status (on/off)
+        self.read_radio_on_off()
+        # Get radio configuration. Note that if both radios are off, the below
+        # code will result in an error
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element='wireless')
+            wireless_button = browser.find_by_id('wireless').first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe('formframe') as iframe:
+                # read if 11ax is enabled first
+                config_item = iframe.find_by_name('enable_ax').first
+                self.ap_settings['enable_ax'] = int(config_item.checked)
+                # read rest of configuration
+                for key, value in self.config_page_fields.items():
+                    if 'bandwidth' in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings[key[0]][key[1]] = self.bw_mode_values[
+                            self.ap_settings['enable_ax']][config_item.value]
+                    elif 'region' in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings['region'] = self.region_map[
+                            config_item.value]
+                    elif 'password' in key:
+                        try:
+                            config_item = iframe.find_by_name(value).first
+                            self.ap_settings[key[0]][
+                                key[1]] = config_item.value
+                            self.ap_settings[
+                                key[0]]['security_type'] = 'WPA2-PSK'
+                        except:
+                            self.ap_settings[key[0]][
+                                key[1]] = 'defaultpassword'
+                            self.ap_settings[
+                                key[0]]['security_type'] = 'Disable'
+                    elif ('channel' in key) or ('ssid' in key):
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings[key[0]][key[1]] = config_item.value
+        return self.ap_settings.copy()
+
+    def configure_ap(self, **config_flags):
+        """Function to configure ap wireless settings."""
+        # Turn radios on or off
+        if config_flags['status_toggled']:
+            self.configure_radio_on_off()
+        # Configure radios
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element='wireless')
+            wireless_button = browser.find_by_id('wireless').first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe('formframe') as iframe:
+                # Create action chain
+                action = selenium.webdriver.common.action_chains.ActionChains(
+                    browser.driver)
+                # Configure 11ax on or off
+                curr_ax_enabled = int(
+                    iframe.find_by_name('enable_ax').first.checked)
+                if self.ap_settings['enable_ax'] != curr_ax_enabled:
+                    ax_checkbox = browser.driver.find_element_by_id(
+                        'enable_ax_chec')
+                    action.move_to_element(ax_checkbox).click().perform()
+                # Update AP region. Must be done before channel setting
+                config_item = iframe.find_by_name(
+                    self.config_page_fields['region']).first
+                config_item.select_by_text(self.ap_settings['region'])
+                # Update wireless settings for each network
+                for key, value in self.config_page_fields.items():
+                    if 'ssid' in key:
+                        config_item = iframe.find_by_name(value).first
+                        config_item.fill(self.ap_settings[key[0]][key[1]])
+                    elif 'channel' in key:
+                        channel = self.ap_settings[key[0]][key[1]]
+                        if int(channel) < 10:
+                            channel_string = '0' + str(channel)
+                        elif int(channel) > 48 and int(channel) < 149:
+                            channel_string = str(channel) + 'DFS'
+                        else:
+                            channel_string = str(channel)
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(channel_string)
+                        except AttributeError:
+                            self.log.warning(
+                                'Cannot select channel. Keeping AP default.')
+                    elif 'bandwidth' in key:
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(
+                                str(self.bw_mode_text[key[0]][self.ap_settings[
+                                    key[0]][key[1]]]))
+                        except AttributeError:
+                            self.log.warning(
+                                'Cannot select bandwidth. Keeping AP default.')
+                # Update passwords for WPA2-PSK protected networks
+                # (Must be done after security type is selected)
+                for key, value in self.config_page_fields.items():
+                    if 'security_type' in key:
+                        security_option = browser.driver.find_element_by_id(
+                            self.security_mode_values[key[0]][self.ap_settings[
+                                key[0]][key[1]]])
+                        action = selenium.webdriver.common.action_chains.ActionChains(
+                            browser.driver)
+                        action.move_to_element(
+                            security_option).click().perform()
+                        if self.ap_settings[key[0]][key[1]] == 'WPA2-PSK':
+                            config_item = iframe.find_by_name(
+                                self.config_page_fields[(key[0],
+                                                         'password')]).first
+                            config_item.fill(
+                                self.ap_settings[key[0]]['password'])
+
+                apply_button = iframe.find_by_name('Apply')
+                apply_button[0].click()
+                time.sleep(BROWSER_WAIT_SHORT)
+                try:
+                    alert = browser.get_alert()
+                    alert.accept()
+                except:
+                    pass
+                time.sleep(BROWSER_WAIT_SHORT)
+                try:
+                    alert = browser.get_alert()
+                    alert.accept()
+                except:
+                    pass
+                time.sleep(BROWSER_WAIT_SHORT)
+            time.sleep(BROWSER_WAIT_EXTRA_LONG)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py
new file mode 100644
index 0000000..91c6382
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py
@@ -0,0 +1,363 @@
+#!/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 collections
+import selenium
+import time
+from acts_contrib.test_utils.wifi.wifi_retail_ap import WifiRetailAP
+from acts_contrib.test_utils.wifi.wifi_retail_ap import BlockingBrowser
+
+BROWSER_WAIT_SHORT = 1
+BROWSER_WAIT_MED = 3
+BROWSER_WAIT_LONG = 30
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+class NetgearRAX200AP(WifiRetailAP):
+    """Class that implements Netgear RAX200 AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Read and update AP settings
+        self.read_ap_settings()
+        self.update_ap_settings(ap_settings)
+
+    def init_gui_data(self):
+        self.config_page = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_wireless_tri_band.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_nologin = (
+            '{protocol}://{ip_address}:{port}/'
+            'WLG_wireless_tri_band.htm').format(
+                protocol=self.ap_settings['protocol'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_advanced = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_adv_tri_band2.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1', '5G_2'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [36, 40, 44, 48, 52, 56, 60, 64],
+                '5G_2': [
+                    100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
+                    149, 153, 157, 161, 165
+                ]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40', 'HE20', 'HE40'],
+                '5G_1': [
+                    'VHT20', 'VHT40', 'VHT80', 'VHT160', 'HE20', 'HE40',
+                    'HE80', 'HE160'
+                ],
+                '5G_2': [
+                    'VHT20', 'VHT40', 'VHT80', 'VHT160', 'HE20', 'HE40',
+                    'HE80', 'HE160'
+                ]
+            },
+            'default_mode': 'HE'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {}
+
+        self.region_map = {
+            '3': 'Australia',
+            '4': 'Canada',
+            '5': 'Europe',
+            '7': 'Japan',
+            '8': 'Korea',
+            '11': 'North America',
+            '16': 'China',
+            '17': 'India',
+            '21': 'Middle East(Saudi Arabia/United Arab Emirates)',
+            '23': 'Singapore',
+            '25': 'Hong Kong',
+            '26': 'Vietnam'
+        }
+
+        self.bw_mode_text = {
+            '2G': {
+                '11g': 'Up to 54 Mbps',
+                'HE20': 'Up to 600 Mbps',
+                'HE40': 'Up to 1200 Mbps',
+                'VHT20': 'Up to 433 Mbps',
+                'VHT40': 'Up to 1000 Mbps'
+            },
+            '5G_1': {
+                'HE20': 'Up to 600 Mbps',
+                'HE40': 'Up to 1200 Mbps',
+                'HE80': 'Up to 2400 Mbps',
+                'HE160': 'Up to 4800 Mbps',
+                'VHT20': 'Up to 433 Mbps',
+                'VHT40': 'Up to 1000 Mbps',
+                'VHT80': 'Up to 2165 Mbps',
+                'VHT160': 'Up to 4330'
+            },
+            '5G_2': {
+                'HE20': 'Up to 600 Mbps',
+                'HE40': 'Up to 1200 Mbps',
+                'HE80': 'Up to 2400 Mbps',
+                'HE160': 'Up to 4800 Mbps',
+                'VHT20': 'Up to 433 Mbps',
+                'VHT40': 'Up to 1000 Mbps',
+                'VHT80': 'Up to 2165 Mbps',
+                'VHT160': 'Up to 4330'
+            }
+        }
+        self.bw_mode_values = {
+            # first key is a boolean indicating if 11ax is enabled
+            0: {
+                'g and b': '11g',
+                '145Mbps': 'VHT20',
+                '300Mbps': 'VHT40',
+                'HT80': 'VHT80',
+                'HT160': 'VHT160'
+            },
+            1: {
+                'g and b': '11g',
+                '145Mbps': 'HE20',
+                '300Mbps': 'HE40',
+                'HT80': 'HE80',
+                'HT160': 'HE160'
+            }
+        }
+
+        # Config ordering intentional to avoid GUI bugs
+        self.config_page_fields = collections.OrderedDict([
+            ('region', 'WRegion'), ('enable_ax', 'enable_he'),
+            (('2G', 'status'), 'enable_ap'),
+            (('5G_1', 'status'), 'enable_ap_an'),
+            (('5G_2', 'status'), 'enable_ap_an_2'), (('2G', 'ssid'), 'ssid'),
+            (('5G_1', 'ssid'), 'ssid_an'), (('5G_2', 'ssid'), 'ssid_an_2'),
+            (('2G', 'channel'), 'w_channel'),
+            (('5G_1', 'channel'), 'w_channel_an'),
+            (('5G_2', 'channel'), 'w_channel_an_2'),
+            (('2G', 'bandwidth'), 'opmode'),
+            (('5G_1', 'bandwidth'), 'opmode_an'),
+            (('5G_2', 'bandwidth'), 'opmode_an_2'),
+            (('2G', 'power'), 'enable_tpc'),
+            (('5G_1', 'power'), 'enable_tpc_an'),
+            (('5G_2', 'power'), 'enable_tpc_an_2'),
+            (('5G_2', 'security_type'), 'security_type_an_2'),
+            (('5G_1', 'security_type'), 'security_type_an'),
+            (('2G', 'security_type'), 'security_type'),
+            (('2G', 'password'), 'passphrase'),
+            (('5G_1', 'password'), 'passphrase_an'),
+            (('5G_2', 'password'), 'passphrase_an_2')
+        ])
+
+        self.power_mode_values = {
+            '1': '100%',
+            '2': '75%',
+            '3': '50%',
+            '4': '25%'
+        }
+
+    def set_bandwidth(self, network, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        if 'bw' in bandwidth:
+            bandwidth = bandwidth.replace('bw',
+                                          self.capabilities['default_mode'])
+        if bandwidth not in self.capabilities['modes'][network]:
+            self.log.error('{} mode is not supported on {} interface.'.format(
+                bandwidth, network))
+        setting_to_update = {network: {'bandwidth': str(bandwidth)}}
+        setting_to_update['enable_ax'] = int('HE' in bandwidth)
+        # Check if other interfaces need to be changed too
+        requested_mode = 'HE' if 'HE' in bandwidth else 'VHT'
+        for other_network in self.capabilities['interfaces']:
+            if other_network == network:
+                continue
+            other_mode = 'HE' if 'HE' in self.ap_settings[other_network][
+                'bandwidth'] else 'VHT'
+            other_bw = ''.join([
+                x for x in self.ap_settings[other_network]['bandwidth']
+                if x.isdigit()
+            ])
+            if other_mode != requested_mode:
+                updated_mode = '{}{}'.format(requested_mode, other_bw)
+                self.log.warning('All networks must be VHT or HE. '
+                                 'Updating {} to {}'.format(
+                                     other_network, updated_mode))
+                setting_to_update.setdefault(other_network, {})
+                setting_to_update[other_network]['bandwidth'] = updated_mode
+
+        self.update_ap_settings(setting_to_update)
+
+    def read_ap_settings(self):
+        """Function to read ap settings."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+
+            for key, value in self.config_page_fields.items():
+                if 'status' in key:
+                    browser.visit_persistent(self.config_page_advanced,
+                                             BROWSER_WAIT_MED, 10)
+                    config_item = browser.find_by_name(value)
+                    self.ap_settings[key[0]][key[1]] = int(
+                        config_item.first.checked)
+                    browser.visit_persistent(self.config_page,
+                                             BROWSER_WAIT_MED, 10)
+                else:
+                    config_item = browser.find_by_name(value)
+                    if 'enable_ax' in key:
+                        self.ap_settings[key] = int(config_item.first.checked)
+                    elif 'bandwidth' in key:
+                        self.ap_settings[key[0]][key[1]] = self.bw_mode_values[
+                            self.ap_settings['enable_ax']][
+                                config_item.first.value]
+                    elif 'power' in key:
+                        self.ap_settings[key[0]][
+                            key[1]] = self.power_mode_values[
+                                config_item.first.value]
+                    elif 'region' in key:
+                        self.ap_settings['region'] = self.region_map[
+                            config_item.first.value]
+                    elif 'security_type' in key:
+                        for item in config_item:
+                            if item.checked:
+                                self.ap_settings[key[0]][key[1]] = item.value
+                    else:
+                        config_item = browser.find_by_name(value)
+                        self.ap_settings[key[0]][
+                            key[1]] = config_item.first.value
+        return self.ap_settings.copy()
+
+    def configure_ap(self, **config_flags):
+        """Function to configure ap wireless settings."""
+        # Turn radios on or off
+        if config_flags['status_toggled']:
+            self.configure_radio_on_off()
+        # Configure radios
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_nologin,
+                                     BROWSER_WAIT_MED, 10, self.config_page)
+
+            # Update region, and power/bandwidth for each network
+            try:
+                config_item = browser.find_by_name(
+                    self.config_page_fields['region']).first
+                config_item.select_by_text(self.ap_settings['region'])
+            except:
+                self.log.warning('Cannot change region.')
+            for key, value in self.config_page_fields.items():
+                if 'enable_ax' in key:
+                    config_item = browser.find_by_name(value).first
+                    if self.ap_settings['enable_ax']:
+                        config_item.check()
+                    else:
+                        config_item.uncheck()
+                if 'power' in key:
+                    config_item = browser.find_by_name(value).first
+                    config_item.select_by_text(
+                        self.ap_settings[key[0]][key[1]])
+                elif 'bandwidth' in key:
+                    config_item = browser.find_by_name(value).first
+                    try:
+                        config_item.select_by_text(self.bw_mode_text[key[0]][
+                            self.ap_settings[key[0]][key[1]]])
+                    except AttributeError:
+                        self.log.warning(
+                            'Cannot select bandwidth. Keeping AP default.')
+
+            # Update security settings (passwords updated only if applicable)
+            for key, value in self.config_page_fields.items():
+                if 'security_type' in key:
+                    browser.choose(value, self.ap_settings[key[0]][key[1]])
+                    if 'WPA' in self.ap_settings[key[0]][key[1]]:
+                        config_item = browser.find_by_name(
+                            self.config_page_fields[(key[0],
+                                                     'password')]).first
+                        config_item.fill(self.ap_settings[key[0]]['password'])
+
+            for key, value in self.config_page_fields.items():
+                if 'ssid' in key:
+                    config_item = browser.find_by_name(value).first
+                    config_item.fill(self.ap_settings[key[0]][key[1]])
+                elif 'channel' in key:
+                    config_item = browser.find_by_name(value).first
+                    try:
+                        config_item.select(self.ap_settings[key[0]][key[1]])
+                        time.sleep(BROWSER_WAIT_SHORT)
+                    except AttributeError:
+                        self.log.warning(
+                            'Cannot select channel. Keeping AP default.')
+                    try:
+                        alert = browser.get_alert()
+                        alert.accept()
+                    except:
+                        pass
+            time.sleep(BROWSER_WAIT_SHORT)
+            browser.find_by_name('Apply').first.click()
+            time.sleep(BROWSER_WAIT_SHORT)
+            try:
+                alert = browser.get_alert()
+                alert.accept()
+                time.sleep(BROWSER_WAIT_SHORT)
+            except:
+                time.sleep(BROWSER_WAIT_SHORT)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+        self.validate_ap_settings()
+
+    def configure_radio_on_off(self):
+        """Helper configuration function to turn radios on/off."""
+        with BlockingBrowser(self.ap_settings['headless_browser'],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED, 10)
+
+            # Turn radios on or off
+            for key, value in self.config_page_fields.items():
+                if 'status' in key:
+                    config_item = browser.find_by_name(value).first
+                    if self.ap_settings[key[0]][key[1]]:
+                        config_item.check()
+                    else:
+                        config_item.uncheck()
+
+            time.sleep(BROWSER_WAIT_SHORT)
+            browser.find_by_name('Apply').first.click()
+            time.sleep(BROWSER_WAIT_EXTRA_LONG)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax80.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax80.py
new file mode 100644
index 0000000..c6c104b
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax80.py
@@ -0,0 +1,81 @@
+#!/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.
+
+from acts_contrib.test_utils.wifi.wifi_retail_ap import NetgearR7000AP
+
+
+class NetgearRAX80AP(NetgearR7000AP):
+    """Class that implements Netgear RAX AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        super().init_gui_data()
+        # Overwrite minor differences from R7000 AP
+        self.config_page = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_wireless_dual_band_r10.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_nologin = (
+            '{protocol}://{ip_address}:{port}/'
+            'WLG_wireless_dual_band_r10.htm').format(
+                protocol=self.ap_settings['protocol'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.config_page_advanced = (
+            '{protocol}://{username}:{password}@'
+            '{ip_address}:{port}/WLG_adv_dual_band2.htm').format(
+                protocol=self.ap_settings['protocol'],
+                username=self.ap_settings['admin_username'],
+                password=self.ap_settings['admin_password'],
+                ip_address=self.ap_settings['ip_address'],
+                port=self.ap_settings['port'])
+        self.capabilities = {
+            'interfaces': ['2G', '5G_1', '5G_2'],
+            'channels': {
+                '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+                '5G_1': [36, 40, 44, 48],
+                '5G_2': [149, 153, 157, 161, 165]
+            },
+            'modes': {
+                '2G': ['VHT20', 'VHT40'],
+                '5G_1': ['VHT20', 'VHT40', 'VHT80'],
+                '5G_2': ['VHT20', 'VHT40', 'VHT80']
+            },
+            'default_mode': 'VHT'
+        }
+        for interface in self.capabilities['interfaces']:
+            self.ap_settings[interface] = {}
+
+        self.bw_mode_values = {
+            'g and b': '11g',
+            '145Mbps': 'VHT20',
+            '300Mbps': 'VHT40',
+            'HT80': 'VHT80',
+            'HT160': 'VHT160'
+        }
+        self.bw_mode_text = {
+            '11g': 'Up to 54 Mbps',
+            'VHT20': 'Up to 600 Mbps',
+            'VHT40': 'Up to 1200 Mbps',
+            'VHT80': 'Up to 2400 Mbps',
+            'VHT160': 'Up to 4800 Mbps'
+        }
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
old mode 100644
new mode 100755
index 67890ce..1729a7c
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
@@ -20,6 +20,8 @@
 import shutil
 import time
 
+from retry import retry
+
 from collections import namedtuple
 from enum import IntEnum
 from queue import Empty
@@ -33,8 +35,10 @@
 from acts.controllers.ap_lib import hostapd_ap_preset
 from acts.controllers.ap_lib.hostapd_constants import BAND_2G
 from acts.controllers.ap_lib.hostapd_constants import BAND_5G
-from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.net import connectivity_const as cconsts
 from acts_contrib.test_utils.tel import tel_defines
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
 
 # Default timeout used for reboot, toggle WiFi and Airplane mode,
 # for the system to settle down after the operation.
@@ -50,47 +54,40 @@
 
 DEFAULT_PING_ADDR = "https://www.google.com/robots.txt"
 
+CNSS_DIAG_CONFIG_PATH = "/data/vendor/wifi/cnss_diag/"
+CNSS_DIAG_CONFIG_FILE = "cnss_diag.conf"
+
 ROAMING_ATTN = {
-        "AP1_on_AP2_off": [
-            0,
-            0,
-            95,
-            95
-        ],
-        "AP1_off_AP2_on": [
-            95,
-            95,
-            0,
-            0
-        ],
-        "default": [
-            0,
-            0,
-            0,
-            0
-        ]
-    }
+    "AP1_on_AP2_off": [0, 0, 95, 95],
+    "AP1_off_AP2_on": [95, 95, 0, 0],
+    "default": [0, 0, 0, 0]
+}
 
 
 class WifiEnums():
 
-    SSID_KEY = "SSID" # Used for Wifi & SoftAp
+    SSID_KEY = "SSID"  # Used for Wifi & SoftAp
     SSID_PATTERN_KEY = "ssidPattern"
     NETID_KEY = "network_id"
-    BSSID_KEY = "BSSID" # Used for Wifi & SoftAp
+    BSSID_KEY = "BSSID"  # Used for Wifi & SoftAp
     BSSID_PATTERN_KEY = "bssidPattern"
-    PWD_KEY = "password" # Used for Wifi & SoftAp
+    PWD_KEY = "password"  # Used for Wifi & SoftAp
     frequency_key = "frequency"
-    HIDDEN_KEY = "hiddenSSID" # Used for Wifi & SoftAp
+    HIDDEN_KEY = "hiddenSSID"  # Used for Wifi & SoftAp
     IS_APP_INTERACTION_REQUIRED = "isAppInteractionRequired"
     IS_USER_INTERACTION_REQUIRED = "isUserInteractionRequired"
     IS_SUGGESTION_METERED = "isMetered"
     PRIORITY = "priority"
-    SECURITY = "security" # Used for Wifi & SoftAp
+    SECURITY = "security"  # Used for Wifi & SoftAp
 
     # Used for SoftAp
     AP_BAND_KEY = "apBand"
     AP_CHANNEL_KEY = "apChannel"
+    AP_BANDS_KEY = "apBands"
+    AP_CHANNEL_FREQUENCYS_KEY = "apChannelFrequencies"
+    AP_MAC_RANDOMIZATION_SETTING_KEY = "MacRandomizationSetting"
+    AP_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLE_KEY = "BridgedModeOpportunisticShutdownEnabled"
+    AP_IEEE80211AX_ENABLED_KEY = "Ieee80211axEnabled"
     AP_MAXCLIENTS_KEY = "MaxNumberOfClients"
     AP_SHUTDOWNTIMEOUT_KEY = "ShutdownTimeoutMillis"
     AP_SHUTDOWNTIMEOUTENABLE_KEY = "AutoShutdownEnabled"
@@ -128,7 +125,9 @@
         WPA3_SAE = "WPA3_SAE"
 
     class CountryCode():
+        AUSTRALIA = "AU"
         CHINA = "CN"
+        GERMANY = "DE"
         JAPAN = "JP"
         UK = "GB"
         US = "US"
@@ -177,6 +176,7 @@
         FRIENDLY_NAME = "providerFriendlyName"
         ROAMING_IDS = "roamingConsortiumIds"
         OCSP = "ocsp"
+
     # End of Macros for EAP
 
     # Macros for wifi p2p.
@@ -284,12 +284,16 @@
     SCAN_TYPE_HIGH_ACCURACY = 2
 
     # US Wifi frequencies
-    ALL_2G_FREQUENCIES = [2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452,
-                          2457, 2462]
-    DFS_5G_FREQUENCIES = [5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580,
-                          5600, 5620, 5640, 5660, 5680, 5700, 5720]
-    NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805,
-                               5825]
+    ALL_2G_FREQUENCIES = [
+        2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462
+    ]
+    DFS_5G_FREQUENCIES = [
+        5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640,
+        5660, 5680, 5700, 5720
+    ]
+    NONE_DFS_5G_FREQUENCIES = [
+        5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805, 5825
+    ]
     ALL_5G_FREQUENCIES = DFS_5G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES
 
     band_to_frequencies = {
@@ -301,6 +305,12 @@
         WIFI_BAND_BOTH_WITH_DFS: ALL_5G_FREQUENCIES + ALL_2G_FREQUENCIES
     }
 
+    # TODO: add all of the band mapping.
+    softap_band_frequencies = {
+        WIFI_CONFIG_SOFTAP_BAND_2G: ALL_2G_FREQUENCIES,
+        WIFI_CONFIG_SOFTAP_BAND_5G: ALL_5G_FREQUENCIES
+    }
+
     # All Wifi frequencies to channels lookup.
     freq_to_channel = {
         2412: 1,
@@ -403,6 +413,7 @@
         44: 5220,
         46: 5230,
         48: 5240,
+        50: 5250,
         52: 5260,
         56: 5280,
         60: 5300,
@@ -438,10 +449,14 @@
 
     def band_to_freq(self, band):
         _band_to_frequencies = {
-            WifiEnums.WIFI_BAND_24_GHZ: self.ALL_2G_FREQUENCIES,
-            WifiEnums.WIFI_BAND_5_GHZ: self.NONE_DFS_5G_FREQUENCIES,
-            WifiEnums.WIFI_BAND_5_GHZ_DFS_ONLY: self.DFS_5G_FREQUENCIES,
-            WifiEnums.WIFI_BAND_5_GHZ_WITH_DFS: self.ALL_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_24_GHZ:
+            self.ALL_2G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_5_GHZ:
+            self.NONE_DFS_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_5_GHZ_DFS_ONLY:
+            self.DFS_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_5_GHZ_WITH_DFS:
+            self.ALL_5G_FREQUENCIES,
             WifiEnums.WIFI_BAND_BOTH:
             self.ALL_2G_FREQUENCIES + self.NONE_DFS_5G_FREQUENCIES,
             WifiEnums.WIFI_BAND_BOTH_WITH_DFS:
@@ -452,17 +467,27 @@
 
 class WifiChannelUS(WifiChannelBase):
     # US Wifi frequencies
-    ALL_2G_FREQUENCIES = [2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452,
-                          2457, 2462]
-    NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805,
-                               5825]
-    MIX_CHANNEL_SCAN = [2412, 2437, 2462, 5180, 5200, 5280, 5260, 5300, 5500,
-                        5320, 5520, 5560, 5700, 5745, 5805]
+    ALL_2G_FREQUENCIES = [
+        2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462
+    ]
+    NONE_DFS_5G_FREQUENCIES = [
+        5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805, 5825
+    ]
+    MIX_CHANNEL_SCAN = [
+        2412, 2437, 2462, 5180, 5200, 5280, 5260, 5300, 5500, 5320, 5520, 5560,
+        5700, 5745, 5805
+    ]
 
-    def __init__(self, model=None):
-        self.DFS_5G_FREQUENCIES = [5260, 5280, 5300, 5320, 5500, 5520,
-                                   5540, 5560, 5580, 5600, 5620, 5640,
-                                   5660, 5680, 5700, 5720]
+    def __init__(self, model=None, support_addition_channel=[]):
+        if model in support_addition_channel:
+            self.ALL_2G_FREQUENCIES = [
+                2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457,
+                2462, 2467, 2472
+                ]
+        self.DFS_5G_FREQUENCIES = [
+            5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580, 5600, 5620,
+            5640, 5660, 5680, 5700, 5720
+            ]
         self.ALL_5G_FREQUENCIES = self.DFS_5G_FREQUENCIES + self.NONE_DFS_5G_FREQUENCIES
 
 
@@ -604,8 +629,10 @@
         If assert_on_fail is False, function returns True if the device transitions
         to the specified state, False otherwise. If assert_on_fail is True, no return value.
     """
-    return _assert_on_fail_handler(
-        _wait_for_wifi_state, assert_on_fail, ad, state=state)
+    return _assert_on_fail_handler(_wait_for_wifi_state,
+                                   assert_on_fail,
+                                   ad,
+                                   state=state)
 
 
 def _wait_for_wifi_state(ad, state):
@@ -622,8 +649,8 @@
         # state change event by mistake.
         return
     ad.droid.wifiStartTrackingStateChange()
-    fail_msg = "Device did not transition to Wi-Fi state to %s on %s." % (state,
-                                                           ad.serial)
+    fail_msg = "Device did not transition to Wi-Fi state to %s on %s." % (
+        state, ad.serial)
     try:
         ad.ed.wait_for_event(wifi_constants.WIFI_STATE_CHANGED,
                              lambda x: x["data"]["enabled"] == state,
@@ -647,8 +674,10 @@
         If assert_on_fail is False, function returns True if the toggle was
         successful, False otherwise. If assert_on_fail is True, no return value.
     """
-    return _assert_on_fail_handler(
-        _wifi_toggle_state, assert_on_fail, ad, new_state=new_state)
+    return _assert_on_fail_handler(_wifi_toggle_state,
+                                   assert_on_fail,
+                                   ad,
+                                   new_state=new_state)
 
 
 def _wifi_toggle_state(ad, new_state=None):
@@ -697,8 +726,13 @@
     networks = ad.droid.wifiGetConfiguredNetworks()
     if not networks:
         return
+    removed = []
     for n in networks:
-        ad.droid.wifiForgetNetwork(n['networkId'])
+        if n['networkId'] not in removed:
+            ad.droid.wifiForgetNetwork(n['networkId'])
+            removed.append(n['networkId'])
+        else:
+            continue
         try:
             event = ad.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS,
                                     SHORT_TIMEOUT)
@@ -710,6 +744,7 @@
         "Failed to remove these configured Wi-Fi networks: %s" % networks)
 
 
+
 def toggle_airplane_mode_on_and_off(ad):
     """Turn ON and OFF Airplane mode.
 
@@ -718,14 +753,12 @@
 
     """
     ad.log.debug("Toggling Airplane mode ON.")
-    asserts.assert_true(
-        utils.force_airplane_mode(ad, True),
-        "Can not turn on airplane mode on: %s" % ad.serial)
+    asserts.assert_true(utils.force_airplane_mode(ad, True),
+                        "Can not turn on airplane mode on: %s" % ad.serial)
     time.sleep(DEFAULT_TIMEOUT)
     ad.log.debug("Toggling Airplane mode OFF.")
-    asserts.assert_true(
-        utils.force_airplane_mode(ad, False),
-        "Can not turn on airplane mode on: %s" % ad.serial)
+    asserts.assert_true(utils.force_airplane_mode(ad, False),
+                        "Can not turn on airplane mode on: %s" % ad.serial)
     time.sleep(DEFAULT_TIMEOUT)
 
 
@@ -755,14 +788,17 @@
     networks = ad.droid.wifiGetConfiguredNetworks()
     if not networks:
         return
+    removed = []
     for n in networks:
-        if net_ssid in n[WifiEnums.SSID_KEY]:
+        if net_ssid in n[WifiEnums.SSID_KEY] and n['networkId'] not in removed:
             ad.droid.wifiForgetNetwork(n['networkId'])
+            removed.append(n['networkId'])
             try:
                 event = ad.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS,
                                         SHORT_TIMEOUT)
             except Empty:
                 asserts.fail("Failed to remove network %s." % n)
+            break
 
 
 def wifi_test_device_init(ad):
@@ -789,14 +825,17 @@
     asserts.assert_equal(ad.droid.wifiGetVerboseLoggingLevel(), 1, msg)
     # We don't verify the following settings since they are not critical.
     # Set wpa_supplicant log level to EXCESSIVE.
-    output = ad.adb.shell("wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
-                          "wlan0 log_level EXCESSIVE")
+    output = ad.adb.shell(
+        "wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
+        "wlan0 log_level EXCESSIVE",
+        ignore_status=True)
     ad.log.info("wpa_supplicant log change status: %s", output)
     utils.sync_device_time(ad)
     ad.droid.telephonyToggleDataConnection(False)
     set_wifi_country_code(ad, WifiEnums.CountryCode.US)
     utils.set_ambient_display(ad, False)
 
+
 def set_wifi_country_code(ad, country_code):
     """Sets the wifi country code on the device.
 
@@ -809,7 +848,7 @@
     """
     try:
         ad.adb.shell("cmd wifi force-country-code enabled %s" % country_code)
-    except ad.adb.AdbError as e:
+    except Exception as e:
         ad.droid.wifiSetCountryCode(WifiEnums.CountryCode.US)
 
 
@@ -841,8 +880,8 @@
     ad.ed.clear_all_events()
     ad.droid.wifiStartScan()
     try:
-        events = ad.ed.pop_events(
-            "WifiManagerScan(ResultsAvailable|Failure)", 60)
+        events = ad.ed.pop_events("WifiManagerScan(ResultsAvailable|Failure)",
+                                  60)
     except Empty:
         asserts.fail(
             "Wi-Fi scan results/failure did not become available within 60s.")
@@ -855,7 +894,8 @@
     return False
 
 
-def start_wifi_connection_scan_and_check_for_network(ad, network_ssid,
+def start_wifi_connection_scan_and_check_for_network(ad,
+                                                     network_ssid,
                                                      max_tries=3):
     """
     Start connectivity scans & checks if the |network_ssid| is seen in
@@ -874,8 +914,8 @@
     for num_tries in range(max_tries):
         if start_wifi_connection_scan_and_return_status(ad):
             scan_results = ad.droid.wifiGetScanResults()
-            match_results = match_networks(
-                {WifiEnums.SSID_KEY: network_ssid}, scan_results)
+            match_results = match_networks({WifiEnums.SSID_KEY: network_ssid},
+                                           scan_results)
             if len(match_results) > 0:
                 ad.log.debug("Found network in %s seconds." %
                              (time.time() - start_time))
@@ -885,8 +925,8 @@
     return False
 
 
-def start_wifi_connection_scan_and_ensure_network_found(ad, network_ssid,
-                                                        max_tries=3):
+def start_wifi_connection_scan_and_ensure_network_found(
+        ad, network_ssid, max_tries=3):
     """
     Start connectivity scans & ensure the |network_ssid| is seen in
     scan results. The method performs a max of |max_tries| connectivity scans
@@ -901,12 +941,13 @@
     ad.log.info("Starting scans to ensure %s is present", network_ssid)
     assert_msg = "Failed to find " + network_ssid + " in scan results" \
         " after " + str(max_tries) + " tries"
-    asserts.assert_true(start_wifi_connection_scan_and_check_for_network(
-        ad, network_ssid, max_tries), assert_msg)
+    asserts.assert_true(
+        start_wifi_connection_scan_and_check_for_network(
+            ad, network_ssid, max_tries), assert_msg)
 
 
-def start_wifi_connection_scan_and_ensure_network_not_found(ad, network_ssid,
-                                                            max_tries=3):
+def start_wifi_connection_scan_and_ensure_network_not_found(
+        ad, network_ssid, max_tries=3):
     """
     Start connectivity scans & ensure the |network_ssid| is not seen in
     scan results. The method performs a max of |max_tries| connectivity scans
@@ -921,8 +962,9 @@
     ad.log.info("Starting scans to ensure %s is not present", network_ssid)
     assert_msg = "Found " + network_ssid + " in scan results" \
         " after " + str(max_tries) + " tries"
-    asserts.assert_false(start_wifi_connection_scan_and_check_for_network(
-        ad, network_ssid, max_tries), assert_msg)
+    asserts.assert_false(
+        start_wifi_connection_scan_and_check_for_network(
+            ad, network_ssid, max_tries), assert_msg)
 
 
 def start_wifi_background_scan(ad, scan_setting):
@@ -941,7 +983,8 @@
     return event['data']
 
 
-def start_wifi_tethering(ad, ssid, password, band=None, hidden=None):
+def start_wifi_tethering(ad, ssid, password, band=None, hidden=None,
+                         security=None):
     """Starts wifi tethering on an android_device.
 
     Args:
@@ -951,6 +994,7 @@
         band: The band the soft AP should be set on. It should be either
             WifiEnums.WIFI_CONFIG_APBAND_2G or WifiEnums.WIFI_CONFIG_APBAND_5G.
         hidden: boolean to indicate if the AP needs to be hidden or not.
+        security: security type of softap.
 
     Returns:
         No return value. Error checks in this function will raise test failure signals
@@ -961,10 +1005,11 @@
     if band:
         config[WifiEnums.AP_BAND_KEY] = band
     if hidden:
-      config[WifiEnums.HIDDEN_KEY] = hidden
-    asserts.assert_true(
-        ad.droid.wifiSetWifiApConfiguration(config),
-        "Failed to update WifiAp Configuration")
+        config[WifiEnums.HIDDEN_KEY] = hidden
+    if security:
+        config[WifiEnums.SECURITY] = security
+    asserts.assert_true(ad.droid.wifiSetWifiApConfiguration(config),
+                        "Failed to update WifiAp Configuration")
     ad.droid.wifiStartTrackingTetherStateChange()
     ad.droid.connectivityStartTethering(tel_defines.TETHERING_WIFI, False)
     try:
@@ -979,13 +1024,24 @@
         ad.droid.wifiStopTrackingTetherStateChange()
 
 
-def save_wifi_soft_ap_config(ad, wifi_config, band=None, hidden=None,
-                             security=None, password=None,
-                             channel=None, max_clients=None,
+def save_wifi_soft_ap_config(ad,
+                             wifi_config,
+                             band=None,
+                             hidden=None,
+                             security=None,
+                             password=None,
+                             channel=None,
+                             max_clients=None,
                              shutdown_timeout_enable=None,
                              shutdown_timeout_millis=None,
                              client_control_enable=None,
-                             allowedList=None, blockedList=None):
+                             allowedList=None,
+                             blockedList=None,
+                             bands=None,
+                             channel_frequencys=None,
+                             mac_randomization_setting=None,
+                             bridged_opportunistic_shutdown_enabled=None,
+                             ieee80211ax_enabled=None):
     """ Save a soft ap configuration and verified
     Args:
         ad: android_device to set soft ap configuration.
@@ -1001,38 +1057,56 @@
         client_control_enable: specifies the client control enable or not.
         allowedList: specifies allowed clients list.
         blockedList: specifies blocked clients list.
+        bands: specifies the band list for the soft ap.
+        channel_frequencys: specifies the channel frequency list for soft ap.
+        mac_randomization_setting: specifies the mac randomization setting.
+        bridged_opportunistic_shutdown_enabled: specifies the opportunistic
+                shutdown enable or not.
+        ieee80211ax_enabled: specifies the ieee80211ax enable or not.
     """
     if security and password:
-       wifi_config[WifiEnums.SECURITY] = security
-       wifi_config[WifiEnums.PWD_KEY] = password
-    if band:
-        wifi_config[WifiEnums.AP_BAND_KEY] = band
-    if hidden:
+        wifi_config[WifiEnums.SECURITY] = security
+        wifi_config[WifiEnums.PWD_KEY] = password
+    if hidden is not None:
         wifi_config[WifiEnums.HIDDEN_KEY] = hidden
-    if channel and band:
-        wifi_config[WifiEnums.AP_BAND_KEY] = band
-        wifi_config[WifiEnums.AP_CHANNEL_KEY] = channel
-    if max_clients:
+    if max_clients is not None:
         wifi_config[WifiEnums.AP_MAXCLIENTS_KEY] = max_clients
-    if shutdown_timeout_enable:
+    if shutdown_timeout_enable is not None:
         wifi_config[
             WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY] = shutdown_timeout_enable
-    if shutdown_timeout_millis:
-        wifi_config[
-            WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] = shutdown_timeout_millis
-    if client_control_enable:
+    if shutdown_timeout_millis is not None:
+        wifi_config[WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] = shutdown_timeout_millis
+    if client_control_enable is not None:
         wifi_config[WifiEnums.AP_CLIENTCONTROL_KEY] = client_control_enable
-    if allowedList:
+    if allowedList is not None:
         wifi_config[WifiEnums.AP_ALLOWEDLIST_KEY] = allowedList
-    if blockedList:
+    if blockedList is not None:
         wifi_config[WifiEnums.AP_BLOCKEDLIST_KEY] = blockedList
+    if mac_randomization_setting is not None:
+        wifi_config[WifiEnums.AP_MAC_RANDOMIZATION_SETTING_KEY
+                ] = mac_randomization_setting
+    if bridged_opportunistic_shutdown_enabled is not None:
+        wifi_config[WifiEnums.AP_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLE_KEY
+                ] = bridged_opportunistic_shutdown_enabled
+    if ieee80211ax_enabled is not None:
+       wifi_config[WifiEnums.AP_IEEE80211AX_ENABLED_KEY]= ieee80211ax_enabled
+    if channel_frequencys is not None:
+        wifi_config[WifiEnums.AP_CHANNEL_FREQUENCYS_KEY] = channel_frequencys
+    elif bands is not None:
+        wifi_config[WifiEnums.AP_BANDS_KEY] = bands
+    elif band is not None:
+        if channel is not None:
+            wifi_config[WifiEnums.AP_BAND_KEY] = band
+            wifi_config[WifiEnums.AP_CHANNEL_KEY] = channel
+        else:
+             wifi_config[WifiEnums.AP_BAND_KEY] = band
 
     if WifiEnums.AP_CHANNEL_KEY in wifi_config and wifi_config[
-        WifiEnums.AP_CHANNEL_KEY] == 0:
+            WifiEnums.AP_CHANNEL_KEY] == 0:
         del wifi_config[WifiEnums.AP_CHANNEL_KEY]
 
     if WifiEnums.SECURITY in wifi_config and wifi_config[
-        WifiEnums.SECURITY] == WifiEnums.SoftApSecurityType.OPEN:
+            WifiEnums.SECURITY] == WifiEnums.SoftApSecurityType.OPEN:
         del wifi_config[WifiEnums.SECURITY]
         del wifi_config[WifiEnums.PWD_KEY]
 
@@ -1057,44 +1131,65 @@
             wifi_ap[WifiEnums.HIDDEN_KEY] == wifi_config[WifiEnums.HIDDEN_KEY],
             "Hotspot hidden setting doesn't match")
 
-    if WifiEnums.AP_BAND_KEY in wifi_config:
-        asserts.assert_true(
-            wifi_ap[WifiEnums.AP_BAND_KEY] == wifi_config[WifiEnums.AP_BAND_KEY],
-            "Hotspot Band doesn't match")
     if WifiEnums.AP_CHANNEL_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_CHANNEL_KEY] == wifi_config[
-            WifiEnums.AP_CHANNEL_KEY], "Hotspot Channel doesn't match")
+                WifiEnums.AP_CHANNEL_KEY], "Hotspot Channel doesn't match")
     if WifiEnums.AP_MAXCLIENTS_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_MAXCLIENTS_KEY] == wifi_config[
-            WifiEnums.AP_MAXCLIENTS_KEY], "Hotspot Max Clients doesn't match")
+                WifiEnums.AP_MAXCLIENTS_KEY],
+            "Hotspot Max Clients doesn't match")
     if WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY] == wifi_config[
-            WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY],
+                WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY],
             "Hotspot ShutDown feature flag doesn't match")
     if WifiEnums.AP_SHUTDOWNTIMEOUT_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] == wifi_config[
-            WifiEnums.AP_SHUTDOWNTIMEOUT_KEY],
+                WifiEnums.AP_SHUTDOWNTIMEOUT_KEY],
             "Hotspot ShutDown timeout setting doesn't match")
     if WifiEnums.AP_CLIENTCONTROL_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_CLIENTCONTROL_KEY] == wifi_config[
-            WifiEnums.AP_CLIENTCONTROL_KEY],
+                WifiEnums.AP_CLIENTCONTROL_KEY],
             "Hotspot Client control flag doesn't match")
     if WifiEnums.AP_ALLOWEDLIST_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_ALLOWEDLIST_KEY] == wifi_config[
-            WifiEnums.AP_ALLOWEDLIST_KEY],
+                WifiEnums.AP_ALLOWEDLIST_KEY],
             "Hotspot Allowed List doesn't match")
     if WifiEnums.AP_BLOCKEDLIST_KEY in wifi_config:
         asserts.assert_true(
             wifi_ap[WifiEnums.AP_BLOCKEDLIST_KEY] == wifi_config[
-            WifiEnums.AP_BLOCKEDLIST_KEY],
+                WifiEnums.AP_BLOCKEDLIST_KEY],
             "Hotspot Blocked List doesn't match")
 
+    if WifiEnums.AP_MAC_RANDOMIZATION_SETTING_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_MAC_RANDOMIZATION_SETTING_KEY] == wifi_config[
+                  WifiEnums.AP_MAC_RANDOMIZATION_SETTING_KEY],
+            "Hotspot Mac randomization setting doesn't match")
+
+    if WifiEnums.AP_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLE_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLE_KEY] == wifi_config[
+                  WifiEnums.AP_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLE_KEY],
+            "Hotspot bridged shutdown enable setting doesn't match")
+
+    if WifiEnums.AP_IEEE80211AX_ENABLED_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_IEEE80211AX_ENABLED_KEY] == wifi_config[
+                  WifiEnums.AP_IEEE80211AX_ENABLED_KEY],
+            "Hotspot 80211 AX enable setting doesn't match")
+
+    if WifiEnums.AP_CHANNEL_FREQUENCYS_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_CHANNEL_FREQUENCYS_KEY] == wifi_config[
+                  WifiEnums.AP_CHANNEL_FREQUENCYS_KEY],
+            "Hotspot channels setting doesn't match")
+
 def start_wifi_tethering_saved_config(ad):
     """ Turn on wifi hotspot with a config that is already saved """
     ad.droid.wifiStartTrackingTetherStateChange()
@@ -1157,12 +1252,11 @@
         If assert_on_fail is False, function returns True if the toggle was
         successful, False otherwise. If assert_on_fail is True, no return value.
     """
-    return _assert_on_fail_handler(
-        _toggle_wifi_and_wait_for_reconnection,
-        assert_on_fail,
-        ad,
-        network,
-        num_of_tries=num_of_tries)
+    return _assert_on_fail_handler(_toggle_wifi_and_wait_for_reconnection,
+                                   assert_on_fail,
+                                   ad,
+                                   network,
+                                   num_of_tries=num_of_tries)
 
 
 def _toggle_wifi_and_wait_for_reconnection(ad, network, num_of_tries=3):
@@ -1206,23 +1300,26 @@
                 break
             except Empty:
                 pass
-        asserts.assert_true(connect_result,
-                            "Failed to connect to Wi-Fi network %s on %s" %
-                            (network, ad.serial))
+        asserts.assert_true(
+            connect_result, "Failed to connect to Wi-Fi network %s on %s" %
+            (network, ad.serial))
         logging.debug("Connection result on %s: %s.", ad.serial,
                       connect_result)
         actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
-        asserts.assert_equal(actual_ssid, expected_ssid,
-                             "Connected to the wrong network on %s."
-                             "Expected %s, but got %s." %
-                             (ad.serial, expected_ssid, actual_ssid))
+        asserts.assert_equal(
+            actual_ssid, expected_ssid, "Connected to the wrong network on %s."
+            "Expected %s, but got %s." %
+            (ad.serial, expected_ssid, actual_ssid))
         logging.info("Connected to Wi-Fi network %s on %s", actual_ssid,
                      ad.serial)
     finally:
         ad.droid.wifiStopTrackingStateChange()
 
 
-def wait_for_connect(ad, expected_ssid=None, expected_id=None, tries=2,
+def wait_for_connect(ad,
+                     expected_ssid=None,
+                     expected_id=None,
+                     tries=2,
                      assert_on_fail=True):
     """Wait for a connect event.
 
@@ -1240,9 +1337,8 @@
         Returns a value only if assert_on_fail is false.
         Returns True if the connection was successful, False otherwise.
     """
-    return _assert_on_fail_handler(
-        _wait_for_connect, assert_on_fail, ad, expected_ssid, expected_id,
-        tries)
+    return _assert_on_fail_handler(_wait_for_connect, assert_on_fail, ad,
+                                   expected_ssid, expected_id, tries)
 
 
 def _wait_for_connect(ad, expected_ssid=None, expected_id=None, tries=2):
@@ -1256,11 +1352,13 @@
     """
     ad.droid.wifiStartTrackingStateChange()
     try:
-        connect_result = _wait_for_connect_event(
-            ad, ssid=expected_ssid, id=expected_id, tries=tries)
-        asserts.assert_true(connect_result,
-                            "Failed to connect to Wi-Fi network %s" %
-                            expected_ssid)
+        connect_result = _wait_for_connect_event(ad,
+                                                 ssid=expected_ssid,
+                                                 id=expected_id,
+                                                 tries=tries)
+        asserts.assert_true(
+            connect_result,
+            "Failed to connect to Wi-Fi network %s" % expected_ssid)
         ad.log.debug("Wi-Fi connection result: %s.", connect_result)
         actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
         if expected_ssid:
@@ -1318,15 +1416,17 @@
     if id is None and ssid is None:
         for i in range(tries):
             try:
-                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
+                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED,
+                                              30)
                 break
             except Empty:
                 pass
     else:
-    # If ssid or network id is specified, wait for specific connect event.
+        # If ssid or network id is specified, wait for specific connect event.
         for i in range(tries):
             try:
-                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
+                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED,
+                                              30)
                 if id and conn_result['data'][WifiEnums.NETID_KEY] == id:
                     break
                 elif ssid and conn_result['data'][WifiEnums.SSID_KEY] == ssid:
@@ -1417,14 +1517,17 @@
     connect_data = ad.droid.wifiGetConnectionInfo()
     connect_ssid = connect_data[WifiEnums.SSID_KEY]
     ad.log.debug("Expected SSID = %s Connected SSID = %s" %
-                   (network_ssid, connect_ssid))
+                 (network_ssid, connect_ssid))
     if connect_ssid != network_ssid:
         return False
     return True
 
 
-def wifi_connect(ad, network, num_of_tries=1, assert_on_fail=True,
-        check_connectivity=True):
+def wifi_connect(ad,
+                 network,
+                 num_of_tries=1,
+                 assert_on_fail=True,
+                 check_connectivity=True):
     """Connect an Android device to a wifi network.
 
     Initiate connection to a wifi network, wait for the "connected" event, then
@@ -1445,9 +1548,12 @@
         Returns a value only if assert_on_fail is false.
         Returns True if the connection was successful, False otherwise.
     """
-    return _assert_on_fail_handler(
-        _wifi_connect, assert_on_fail, ad, network, num_of_tries=num_of_tries,
-          check_connectivity=check_connectivity)
+    return _assert_on_fail_handler(_wifi_connect,
+                                   assert_on_fail,
+                                   ad,
+                                   network,
+                                   num_of_tries=num_of_tries,
+                                   check_connectivity=check_connectivity)
 
 
 def _wifi_connect(ad, network, num_of_tries=1, check_connectivity=True):
@@ -1465,32 +1571,33 @@
         num_of_tries: An integer that is the number of times to try before
                       delaring failure. Default is 1.
     """
-    asserts.assert_true(WifiEnums.SSID_KEY in network,
-                        "Key '%s' must be present in network definition." %
-                        WifiEnums.SSID_KEY)
+    asserts.assert_true(
+        WifiEnums.SSID_KEY in network,
+        "Key '%s' must be present in network definition." % WifiEnums.SSID_KEY)
     ad.droid.wifiStartTrackingStateChange()
     expected_ssid = network[WifiEnums.SSID_KEY]
     ad.droid.wifiConnectByConfig(network)
     ad.log.info("Starting connection process to %s", expected_ssid)
     try:
         event = ad.ed.pop_event(wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 30)
-        connect_result = _wait_for_connect_event(
-            ad, ssid=expected_ssid, tries=num_of_tries)
-        asserts.assert_true(connect_result,
-                            "Failed to connect to Wi-Fi network %s on %s" %
-                            (network, ad.serial))
+        connect_result = _wait_for_connect_event(ad,
+                                                 ssid=expected_ssid,
+                                                 tries=num_of_tries)
+        asserts.assert_true(
+            connect_result, "Failed to connect to Wi-Fi network %s on %s" %
+            (network, ad.serial))
         ad.log.debug("Wi-Fi connection result: %s.", connect_result)
         actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
-        asserts.assert_equal(actual_ssid, expected_ssid,
-                             "Connected to the wrong network on %s." %
-                             ad.serial)
+        asserts.assert_equal(
+            actual_ssid, expected_ssid,
+            "Connected to the wrong network on %s." % ad.serial)
         ad.log.info("Connected to Wi-Fi network %s.", actual_ssid)
 
         if check_connectivity:
             internet = validate_connection(ad, DEFAULT_PING_ADDR)
             if not internet:
-                raise signals.TestFailure("Failed to connect to internet on %s" %
-                                          expected_ssid)
+                raise signals.TestFailure(
+                    "Failed to connect to internet on %s" % expected_ssid)
     except Empty:
         asserts.fail("Failed to start connection process to %s on %s" %
                      (network, ad.serial))
@@ -1546,19 +1653,21 @@
     ad.log.info("Starting connection to network with id %d", network_id)
     try:
         event = ad.ed.pop_event(wifi_constants.CONNECT_BY_NETID_SUCCESS, 60)
-        connect_result = _wait_for_connect_event(
-            ad, id=network_id, tries=num_of_tries)
-        asserts.assert_true(connect_result,
-                            "Failed to connect to Wi-Fi network using network id")
+        connect_result = _wait_for_connect_event(ad,
+                                                 id=network_id,
+                                                 tries=num_of_tries)
+        asserts.assert_true(
+            connect_result,
+            "Failed to connect to Wi-Fi network using network id")
         ad.log.debug("Wi-Fi connection result: %s", connect_result)
         actual_id = connect_result['data'][WifiEnums.NETID_KEY]
-        asserts.assert_equal(actual_id, network_id,
-                             "Connected to the wrong network on %s."
-                             "Expected network id = %d, but got %d." %
-                             (ad.serial, network_id, actual_id))
+        asserts.assert_equal(
+            actual_id, network_id, "Connected to the wrong network on %s."
+            "Expected network id = %d, but got %d." %
+            (ad.serial, network_id, actual_id))
         expected_ssid = connect_result['data'][WifiEnums.SSID_KEY]
         ad.log.info("Connected to Wi-Fi network %s with %d network id.",
-                     expected_ssid, network_id)
+                    expected_ssid, network_id)
 
         internet = validate_connection(ad, DEFAULT_PING_ADDR)
         if not internet:
@@ -1566,18 +1675,20 @@
                                       expected_ssid)
     except Empty:
         asserts.fail("Failed to connect to network with id %d on %s" %
-                    (network_id, ad.serial))
+                     (network_id, ad.serial))
     except Exception as error:
         ad.log.error("Failed to connect to network with id %d with error %s",
-                      network_id, error)
+                     network_id, error)
         raise signals.TestFailure("Failed to connect to network with network"
                                   " id %d" % network_id)
     finally:
         ad.droid.wifiStopTrackingStateChange()
 
 
-def wifi_connect_using_network_request(ad, network, network_specifier,
-                                       num_of_tries=3, assert_on_fail=True):
+def wifi_connect_using_network_request(ad,
+                                       network,
+                                       network_specifier,
+                                       num_of_tries=3):
     """Connect an Android device to a wifi network using network request.
 
     Trigger a network request with the provided network specifier,
@@ -1586,8 +1697,6 @@
     request with the specified network selected. Then wait for the "onAvailable"
     network callback indicating successful connection to network.
 
-    This will directly fail a test if anything goes wrong.
-
     Args:
         ad: android_device object to initiate connection on.
         network_specifier: A dictionary representing the network specifier to
@@ -1596,45 +1705,22 @@
                  dictionary must have the key "SSID".
         num_of_tries: An integer that is the number of times to try before
                       delaring failure.
-        assert_on_fail: If True, error checks in this function will raise test
-                        failure signals.
-
     Returns:
-        Returns a value only if assert_on_fail is false.
-        Returns True if the connection was successful, False otherwise.
+        key: Key corresponding to network request.
     """
-    _assert_on_fail_handler(_wifi_connect_using_network_request, assert_on_fail,
-                            ad, network, network_specifier, num_of_tries)
-
-
-def _wifi_connect_using_network_request(ad, network, network_specifier,
-                                        num_of_tries=3):
-    """Connect an Android device to a wifi network using network request.
-
-    Trigger a network request with the provided network specifier,
-    wait for the "onMatch" event, ensure that the scan results in "onMatch"
-    event contain the specified network, then simulate the user granting the
-    request with the specified network selected. Then wait for the "onAvailable"
-    network callback indicating successful connection to network.
-
-    Args:
-        ad: android_device object to initiate connection on.
-        network_specifier: A dictionary representing the network specifier to
-                           use.
-        network: A dictionary representing the network to connect to. The
-                 dictionary must have the key "SSID".
-        num_of_tries: An integer that is the number of times to try before
-                      delaring failure.
-    """
-    ad.droid.wifiRequestNetworkWithSpecifier(network_specifier)
-    ad.log.info("Sent network request with %s", network_specifier)
+    key = ad.droid.connectivityRequestWifiNetwork(network_specifier, 0)
+    ad.log.info("Sent network request %s with %s " % (key, network_specifier))
     # Need a delay here because UI interaction should only start once wifi
     # starts processing the request.
     time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
-    _wait_for_wifi_connect_after_network_request(ad, network, num_of_tries)
+    _wait_for_wifi_connect_after_network_request(ad, network, key, num_of_tries)
+    return key
 
 
-def wait_for_wifi_connect_after_network_request(ad, network, num_of_tries=3,
+def wait_for_wifi_connect_after_network_request(ad,
+                                                network,
+                                                key,
+                                                num_of_tries=3,
                                                 assert_on_fail=True):
     """
     Simulate and verify the connection flow after initiating the network
@@ -1649,6 +1735,7 @@
         ad: android_device object to initiate connection on.
         network: A dictionary representing the network to connect to. The
                  dictionary must have the key "SSID".
+        key: Key corresponding to network request.
         num_of_tries: An integer that is the number of times to try before
                       delaring failure.
         assert_on_fail: If True, error checks in this function will raise test
@@ -1659,10 +1746,10 @@
         Returns True if the connection was successful, False otherwise.
     """
     _assert_on_fail_handler(_wait_for_wifi_connect_after_network_request,
-                            assert_on_fail, ad, network, num_of_tries)
+                            assert_on_fail, ad, network, key, num_of_tries)
 
 
-def _wait_for_wifi_connect_after_network_request(ad, network, num_of_tries=3):
+def _wait_for_wifi_connect_after_network_request(ad, network, key, num_of_tries=3):
     """
     Simulate and verify the connection flow after initiating the network
     request.
@@ -1676,12 +1763,13 @@
         ad: android_device object to initiate connection on.
         network: A dictionary representing the network to connect to. The
                  dictionary must have the key "SSID".
+        key: Key corresponding to network request.
         num_of_tries: An integer that is the number of times to try before
                       delaring failure.
     """
-    asserts.assert_true(WifiEnums.SSID_KEY in network,
-                        "Key '%s' must be present in network definition." %
-                        WifiEnums.SSID_KEY)
+    asserts.assert_true(
+        WifiEnums.SSID_KEY in network,
+        "Key '%s' must be present in network definition." % WifiEnums.SSID_KEY)
     ad.droid.wifiStartTrackingStateChange()
     expected_ssid = network[WifiEnums.SSID_KEY]
     ad.droid.wifiRegisterNetworkRequestMatchCallback()
@@ -1689,7 +1777,7 @@
     # matching the request
     try:
         matched_network = None
-        for _ in [0,  num_of_tries]:
+        for _ in [0, num_of_tries]:
             on_match_event = ad.ed.pop_event(
                 wifi_constants.WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH, 60)
             asserts.assert_true(on_match_event,
@@ -1700,39 +1788,57 @@
             matched_network = match_networks(
                 {WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]},
                 matched_scan_results)
+            ad.log.debug("Network request on match %s", matched_network)
             if matched_network:
-                break;
+                break
 
-        asserts.assert_true(
-            matched_network, "Target network %s not found" % network)
+        asserts.assert_true(matched_network,
+                            "Target network %s not found" % network)
 
         ad.droid.wifiSendUserSelectionForNetworkRequestMatch(network)
         ad.log.info("Sent user selection for network request %s",
                     expected_ssid)
 
         # Wait for the platform to connect to the network.
-        on_available_event = ad.ed.pop_event(
-            wifi_constants.WIFI_NETWORK_CB_ON_AVAILABLE, 60)
-        asserts.assert_true(on_available_event,
-                            "Network request on available not received.")
-        connected_network = on_available_event["data"]
+        autils.wait_for_event_with_keys(
+            ad, cconsts.EVENT_NETWORK_CALLBACK,
+            60,
+            (cconsts.NETWORK_CB_KEY_ID, key),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_AVAILABLE))
+        on_capabilities_changed = autils.wait_for_event_with_keys(
+            ad, cconsts.EVENT_NETWORK_CALLBACK,
+            10,
+            (cconsts.NETWORK_CB_KEY_ID, key),
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_CAPABILITIES_CHANGED))
+        connected_network = None
+        # WifiInfo is attached to TransportInfo only in S.
+        if ad.droid.isSdkAtLeastS():
+            connected_network = (
+                on_capabilities_changed["data"][
+                    cconsts.NETWORK_CB_KEY_TRANSPORT_INFO]
+            )
+        else:
+            connected_network = ad.droid.wifiGetConnectionInfo()
         ad.log.info("Connected to network %s", connected_network)
-        asserts.assert_equal(connected_network[WifiEnums.SSID_KEY],
-                             expected_ssid,
-                             "Connected to the wrong network."
-                             "Expected %s, but got %s." %
-                             (network, connected_network))
+        asserts.assert_equal(
+            connected_network[WifiEnums.SSID_KEY], expected_ssid,
+            "Connected to the wrong network."
+            "Expected %s, but got %s."
+            % (network, connected_network))
     except Empty:
         asserts.fail("Failed to connect to %s" % expected_ssid)
     except Exception as error:
-        ad.log.error("Failed to connect to %s with error %s",
+        ad.log.error("Failed to connect to %s with error %s" %
                      (expected_ssid, error))
         raise signals.TestFailure("Failed to connect to %s network" % network)
     finally:
         ad.droid.wifiStopTrackingStateChange()
 
 
-def wifi_passpoint_connect(ad, passpoint_network, num_of_tries=1,
+def wifi_passpoint_connect(ad,
+                           passpoint_network,
+                           num_of_tries=1,
                            assert_on_fail=True):
     """Connect an Android device to a wifi network.
 
@@ -1753,8 +1859,11 @@
         If assert_on_fail is False, function returns network id, if the connect was
         successful, False otherwise. If assert_on_fail is True, no return value.
     """
-    _assert_on_fail_handler(_wifi_passpoint_connect, assert_on_fail, ad,
-                            passpoint_network, num_of_tries = num_of_tries)
+    _assert_on_fail_handler(_wifi_passpoint_connect,
+                            assert_on_fail,
+                            ad,
+                            passpoint_network,
+                            num_of_tries=num_of_tries)
 
 
 def _wifi_passpoint_connect(ad, passpoint_network, num_of_tries=1):
@@ -1776,15 +1885,16 @@
     ad.log.info("Starting connection process to passpoint %s", expected_ssid)
 
     try:
-        connect_result = _wait_for_connect_event(
-            ad, expected_ssid, num_of_tries)
-        asserts.assert_true(connect_result,
-                            "Failed to connect to WiFi passpoint network %s on"
-                            " %s" % (expected_ssid, ad.serial))
+        connect_result = _wait_for_connect_event(ad, expected_ssid,
+                                                 num_of_tries)
+        asserts.assert_true(
+            connect_result, "Failed to connect to WiFi passpoint network %s on"
+            " %s" % (expected_ssid, ad.serial))
         ad.log.info("Wi-Fi connection result: %s.", connect_result)
         actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
-        asserts.assert_equal(actual_ssid, expected_ssid,
-                             "Connected to the wrong network on %s." % ad.serial)
+        asserts.assert_equal(
+            actual_ssid, expected_ssid,
+            "Connected to the wrong network on %s." % ad.serial)
         ad.log.info("Connected to Wi-Fi passpoint network %s.", actual_ssid)
 
         internet = validate_connection(ad, DEFAULT_PING_ADDR)
@@ -1793,9 +1903,9 @@
                                       expected_ssid)
     except Exception as error:
         ad.log.error("Failed to connect to passpoint network %s with error %s",
-                      expected_ssid, error)
+                     expected_ssid, error)
         raise signals.TestFailure("Failed to connect to %s passpoint network" %
-                                   expected_ssid)
+                                  expected_ssid)
 
     finally:
         ad.droid.wifiStopTrackingStateChange()
@@ -1807,8 +1917,9 @@
         ad.droid.removePasspointConfig(fqdn)
         return True
     except Exception as error:
-        ad.log.error("Failed to remove passpoint configuration with FQDN=%s "
-                     "and error=%s" , fqdn, error)
+        ad.log.error(
+            "Failed to remove passpoint configuration with FQDN=%s "
+            "and error=%s", fqdn, error)
         return False
 
 
@@ -1907,14 +2018,16 @@
         filename.
     """
     asserts.assert_true(in_file.endswith(".pem"), "Input file has to be .pem.")
-    asserts.assert_true(
-        out_file.endswith(".der"), "Output file has to be .der.")
+    asserts.assert_true(out_file.endswith(".der"),
+                        "Output file has to be .der.")
     cmd = ("openssl pkcs8 -inform PEM -in {} -outform DER -out {} -nocrypt"
            " -topk8").format(in_file, out_file)
     utils.exe_cmd(cmd)
 
 
-def validate_connection(ad, ping_addr=DEFAULT_PING_ADDR, wait_time=15,
+def validate_connection(ad,
+                        ping_addr=DEFAULT_PING_ADDR,
+                        wait_time=15,
                         ping_gateway=True):
     """Validate internet connection by pinging the address provided.
 
@@ -1928,7 +2041,8 @@
     """
     # wait_time to allow for DHCP to complete.
     for i in range(wait_time):
-        if ad.droid.connectivityNetworkIsConnected():
+        if ad.droid.connectivityNetworkIsConnected(
+        ) and ad.droid.connectivityGetIPv4DefaultGateway():
             break
         time.sleep(1)
     ping = False
@@ -1977,7 +2091,8 @@
             raise signals.TestFailure(msg)
 
 
-def check_autoconnect_to_open_network(ad, conn_timeout=WIFI_CONNECTION_TIMEOUT_DEFAULT):
+def check_autoconnect_to_open_network(
+        ad, conn_timeout=WIFI_CONNECTION_TIMEOUT_DEFAULT):
     """Connects to any open WiFI AP
      Args:
          timeout value in sec to wait for UE to connect to a WiFi AP
@@ -1991,8 +2106,8 @@
     wifi_connection_state = None
     timeout = time.time() + conn_timeout
     while wifi_connection_state != "completed":
-        wifi_connection_state = ad.droid.wifiGetConnectionInfo()[
-            'supplicant_state']
+        wifi_connection_state = ad.droid.wifiGetConnectionInfo(
+        )['supplicant_state']
         if time.time() > timeout:
             ad.log.warning("Failed to connect to WiFi AP")
             return False
@@ -2016,8 +2131,8 @@
         phase2_types = [WifiEnums.EapPhase2.GTC, WifiEnums.EapPhase2.MSCHAPV2]
     for phase2_type in phase2_types:
         # Skip a special case for passpoint TTLS.
-        if (WifiEnums.Enterprise.FQDN in config and
-                phase2_type == WifiEnums.EapPhase2.GTC):
+        if (WifiEnums.Enterprise.FQDN in config
+                and phase2_type == WifiEnums.EapPhase2.GTC):
             continue
         c = dict(config)
         c[WifiEnums.Enterprise.PHASE2] = phase2_type.value
@@ -2114,9 +2229,10 @@
         attenuator[3].set_atten(roaming_attn[attn_val_name][3])
     except:
         logging.exception("Failed to set attenuation values %s.",
-                       attn_val_name)
+                          attn_val_name)
         raise
 
+
 def set_attns_steps(attenuators,
                     atten_val_name,
                     roaming_attn=ROAMING_ATTN,
@@ -2135,7 +2251,7 @@
         wait_time: Sleep time for each change of attenuator.
     """
     logging.info("Set attenuation values to %s in %d step(s)",
-            roaming_attn[atten_val_name], steps)
+                 roaming_attn[atten_val_name], steps)
     start_atten = [attenuator.get_atten() for attenuator in attenuators]
     target_atten = roaming_attn[atten_val_name]
     for current_step in range(steps):
@@ -2171,7 +2287,8 @@
     logging.info("Roamed to %s successfully", expected_bssid)
     if not validate_connection(dut):
         raise signals.TestFailure("Fail to connect to internet on %s" %
-                                      expected_bssid)
+                                  expected_bssid)
+
 
 def create_softap_config():
     """Create a softap config with random ssid and password."""
@@ -2184,6 +2301,7 @@
     }
     return config
 
+
 def start_softap_and_verify(ad, band):
     """Bring-up softap and verify AP mode and in scan results.
 
@@ -2203,11 +2321,12 @@
     config = create_softap_config()
     start_wifi_tethering(ad.dut,
                          config[WifiEnums.SSID_KEY],
-                         config[WifiEnums.PWD_KEY], band=band)
+                         config[WifiEnums.PWD_KEY],
+                         band=band)
     asserts.assert_true(ad.dut.droid.wifiIsApEnabled(),
-                         "SoftAp is not reported as running")
-    start_wifi_connection_scan_and_ensure_network_found(ad.dut_client,
-        config[WifiEnums.SSID_KEY])
+                        "SoftAp is not reported as running")
+    start_wifi_connection_scan_and_ensure_network_found(
+        ad.dut_client, config[WifiEnums.SSID_KEY])
 
     # Check softap info can get from callback succeed and assert value should be
     # valid.
@@ -2219,24 +2338,30 @@
 
     return config
 
+
 def wait_for_expected_number_of_softap_clients(ad, callbackId,
-        expected_num_of_softap_clients):
+                                               expected_num_of_softap_clients):
     """Wait for the number of softap clients to be updated as expected.
     Args:
         callbackId: Id of the callback associated with registering.
         expected_num_of_softap_clients: expected number of softap clients.
     """
     eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
-            callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
+        callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
     clientData = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)['data']
     clientCount = clientData[wifi_constants.SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
-    clientMacAddresses = clientData[wifi_constants.SOFTAP_CLIENTS_MACS_CALLBACK_KEY]
-    asserts.assert_equal(clientCount, expected_num_of_softap_clients,
-            "The number of softap clients doesn't match the expected number")
-    asserts.assert_equal(len(clientMacAddresses), expected_num_of_softap_clients,
-                         "The number of mac addresses doesn't match the expected number")
+    clientMacAddresses = clientData[
+        wifi_constants.SOFTAP_CLIENTS_MACS_CALLBACK_KEY]
+    asserts.assert_equal(
+        clientCount, expected_num_of_softap_clients,
+        "The number of softap clients doesn't match the expected number")
+    asserts.assert_equal(
+        len(clientMacAddresses), expected_num_of_softap_clients,
+        "The number of mac addresses doesn't match the expected number")
     for macAddress in clientMacAddresses:
-        asserts.assert_true(checkMacAddress(macAddress), "An invalid mac address was returned")
+        asserts.assert_true(checkMacAddress(macAddress),
+                            "An invalid mac address was returned")
+
 
 def checkMacAddress(input):
     """Validate whether a string is a valid mac address or not.
@@ -2251,6 +2376,7 @@
         return True
     return False
 
+
 def wait_for_expected_softap_state(ad, callbackId, expected_softap_state):
     """Wait for the expected softap state change.
     Args:
@@ -2258,12 +2384,13 @@
         expected_softap_state: The expected softap state.
     """
     eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
-            callbackId) + wifi_constants.SOFTAP_STATE_CHANGED
-    asserts.assert_equal(ad.ed.pop_event(eventStr,
-            SHORT_TIMEOUT)['data'][wifi_constants.
-            SOFTAP_STATE_CHANGE_CALLBACK_KEY],
-            expected_softap_state,
-            "Softap state doesn't match with expected state")
+        callbackId) + wifi_constants.SOFTAP_STATE_CHANGED
+    asserts.assert_equal(
+        ad.ed.pop_event(eventStr, SHORT_TIMEOUT)['data'][
+            wifi_constants.SOFTAP_STATE_CHANGE_CALLBACK_KEY],
+        expected_softap_state,
+        "Softap state doesn't match with expected state")
+
 
 def get_current_number_of_softap_clients(ad, callbackId):
     """pop up all of softap client updated event from queue.
@@ -2275,64 +2402,118 @@
         Returns None when no any match callback event in queue.
     """
     eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
-            callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
+        callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
     events = ad.ed.pop_all(eventStr)
     for event in events:
-        num_of_clients = event['data'][wifi_constants.
-                SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
+        num_of_clients = event['data'][
+            wifi_constants.SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
     if len(events) == 0:
         return None
     return num_of_clients
 
-def get_current_softap_info(ad, callbackId, least_one):
+
+def get_current_softap_info(ad, callbackId, need_to_wait):
     """pop up all of softap info changed event from queue.
     Args:
         callbackId: Id of the callback associated with registering.
-        least_one: Wait for the info callback event before pop all.
+        need_to_wait: Wait for the info callback event before pop all.
     Returns:
         Returns last updated information of softap.
     """
     eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
-            callbackId) + wifi_constants.SOFTAP_INFO_CHANGED
-    ad.log.info("softap info dump from eventStr %s",
-                eventStr)
+        callbackId) + wifi_constants.SOFTAP_INFO_CHANGED
+    ad.log.debug("softap info dump from eventStr %s", eventStr)
     frequency = 0
     bandwidth = 0
-    if (least_one):
+    if (need_to_wait):
         event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)
-        frequency = event['data'][wifi_constants.
-                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
-        bandwidth = event['data'][wifi_constants.
-                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+        frequency = event['data'][
+            wifi_constants.SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = event['data'][
+            wifi_constants.SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
         ad.log.info("softap info updated, frequency is %s, bandwidth is %s",
-            frequency, bandwidth)
+                    frequency, bandwidth)
 
     events = ad.ed.pop_all(eventStr)
     for event in events:
-        frequency = event['data'][wifi_constants.
-                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
-        bandwidth = event['data'][wifi_constants.
-                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
-    ad.log.info("softap info, frequency is %s, bandwidth is %s",
-            frequency, bandwidth)
+        frequency = event['data'][
+            wifi_constants.SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = event['data'][
+            wifi_constants.SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+    ad.log.info("softap info, frequency is %s, bandwidth is %s", frequency,
+                bandwidth)
     return frequency, bandwidth
 
+def get_current_softap_infos(ad, callbackId, need_to_wait):
+    """pop up all of softap info list changed event from queue.
+    Args:
+        callbackId: Id of the callback associated with registering.
+        need_to_wait: Wait for the info callback event before pop all.
+    Returns:
+        Returns last updated informations of softap.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+        callbackId) + wifi_constants.SOFTAP_INFOLIST_CHANGED
+    ad.log.debug("softap info dump from eventStr %s", eventStr)
 
+    if (need_to_wait):
+        event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)
+        infos = event['data']
 
-def get_ssrdumps(ad, test_name=""):
+    events = ad.ed.pop_all(eventStr)
+    for event in events:
+        infos = event['data']
+
+    for info in infos:
+        frequency = info[
+            wifi_constants.SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = info[
+            wifi_constants.SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+        wifistandard = info[
+            wifi_constants.SOFTAP_INFO_WIFISTANDARD_CALLBACK_KEY]
+        bssid = info[
+            wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+        ad.log.info(
+                "softap info, freq:%s, bw:%s, wifistandard:%s, bssid:%s",
+                frequency, bandwidth, wifistandard, bssid)
+
+    return infos
+
+def get_current_softap_capability(ad, callbackId, need_to_wait):
+    """pop up all of softap info list changed event from queue.
+    Args:
+        callbackId: Id of the callback associated with registering.
+        need_to_wait: Wait for the info callback event before pop all.
+    Returns:
+        Returns last updated capability of softap.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_CAPABILITY_CHANGED
+    ad.log.debug("softap capability dump from eventStr %s", eventStr)
+    if (need_to_wait):
+        event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)
+        capability = event['data']
+
+    events = ad.ed.pop_all(eventStr)
+    for event in events:
+        capability = event['data']
+
+    return capability
+
+def get_ssrdumps(ad):
     """Pulls dumps in the ssrdump dir
     Args:
         ad: android device object.
-        test_name: test case name
     """
     logs = ad.get_file_names("/data/vendor/ssrdump/")
     if logs:
         ad.log.info("Pulling ssrdumps %s", logs)
-        log_path = os.path.join(ad.log_path, test_name,
-                                "SSRDUMP_%s" % ad.serial)
+        log_path = os.path.join(ad.device_log_path, "SSRDUMPS_%s" % ad.serial)
         os.makedirs(log_path, exist_ok=True)
         ad.pull_files(logs, log_path)
-    ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete")
+    ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete",
+                 ignore_status=True)
+
 
 def start_pcap(pcap, wifi_band, test_name):
     """Start packet capture in monitor mode.
@@ -2378,6 +2559,7 @@
     if test_status:
         shutil.rmtree(os.path.dirname(fname))
 
+
 def verify_mac_not_found_in_pcap(ad, mac, packets):
     """Verify that a mac address is not found in the captured packets.
 
@@ -2392,6 +2574,7 @@
             asserts.fail("Device %s caught Factory MAC: %s in packet sniffer."
                          "Packet = %s" % (ad.serial, mac, pkt.show()))
 
+
 def verify_mac_is_found_in_pcap(ad, mac, packets):
     """Verify that a mac address is found in the captured packets.
 
@@ -2406,37 +2589,53 @@
     asserts.fail("Did not find MAC = %s in packet sniffer."
                  "for device %s" % (mac, ad.serial))
 
-def start_cnss_diags(ads):
+
+def start_cnss_diags(ads, cnss_diag_file, pixel_models):
     for ad in ads:
-        start_cnss_diag(ad)
+        start_cnss_diag(ad, cnss_diag_file, pixel_models)
 
 
-def start_cnss_diag(ad):
+def start_cnss_diag(ad, cnss_diag_file, pixel_models):
     """Start cnss_diag to record extra wifi logs
 
     Args:
         ad: android device object.
+        cnss_diag_file: cnss diag config file to push to device.
+        pixel_models: pixel devices.
     """
+    if ad.model not in pixel_models:
+        ad.log.info("Device not supported to collect pixel logger")
+        return
     if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
         prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
     else:
         prop = wifi_constants.CNSS_DIAG_PROP
     if ad.adb.getprop(prop) != 'true':
-        ad.adb.shell("find /data/vendor/wifi/cnss_diag/wlan_logs/ -type f -delete")
+        if not int(
+                ad.adb.shell("ls -l %s%s | wc -l" %
+                             (CNSS_DIAG_CONFIG_PATH, CNSS_DIAG_CONFIG_FILE))):
+            ad.adb.push("%s %s" % (cnss_diag_file, CNSS_DIAG_CONFIG_PATH))
+        ad.adb.shell(
+            "find /data/vendor/wifi/cnss_diag/wlan_logs/ -type f -delete",
+            ignore_status=True)
         ad.adb.shell("setprop %s true" % prop, ignore_status=True)
 
 
-def stop_cnss_diags(ads):
+def stop_cnss_diags(ads, pixel_models):
     for ad in ads:
-        stop_cnss_diag(ad)
+        stop_cnss_diag(ad, pixel_models)
 
 
-def stop_cnss_diag(ad):
+def stop_cnss_diag(ad, pixel_models):
     """Stops cnss_diag
 
     Args:
         ad: android device object.
+        pixel_models: pixel devices.
     """
+    if ad.model not in pixel_models:
+        ad.log.info("Device not supported to collect pixel logger")
+        return
     if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
         prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
     else:
@@ -2444,11 +2643,10 @@
     ad.adb.shell("setprop %s false" % prop, ignore_status=True)
 
 
-def get_cnss_diag_log(ad, test_name=""):
+def get_cnss_diag_log(ad):
     """Pulls the cnss_diag logs in the wlan_logs dir
     Args:
         ad: android device object.
-        test_name: test case name
     """
     logs = ad.get_file_names("/data/vendor/wifi/cnss_diag/wlan_logs/")
     if logs:
@@ -2458,8 +2656,9 @@
         ad.pull_files(logs, log_path)
 
 
-LinkProbeResult = namedtuple('LinkProbeResult', (
-    'is_success', 'stdout', 'elapsed_time', 'failure_reason'))
+LinkProbeResult = namedtuple(
+    'LinkProbeResult',
+    ('is_success', 'stdout', 'elapsed_time', 'failure_reason'))
 
 
 def send_link_probe(ad):
@@ -2488,9 +2687,10 @@
     else:
         asserts.fail('Unexpected link probe result: ' + stdout)
 
-    return LinkProbeResult(
-        is_success=is_success, stdout=stdout,
-        elapsed_time=elapsed_time, failure_reason=failure_reason)
+    return LinkProbeResult(is_success=is_success,
+                           stdout=stdout,
+                           elapsed_time=elapsed_time,
+                           failure_reason=failure_reason)
 
 
 def send_link_probes(ad, num_probes, delay_sec):
@@ -2518,7 +2718,7 @@
 
 
 def ap_setup(test, index, ap, network, bandwidth=80, channel=6):
-        """Set up the AP with provided network info.
+    """Set up the AP with provided network info.
 
         Args:
             test: the calling test class object.
@@ -2531,29 +2731,28 @@
         Returns:
             brconfigs: the bridge interface configs
         """
-        bss_settings = []
-        ssid = network[WifiEnums.SSID_KEY]
-        test.access_points[index].close()
-        time.sleep(5)
+    bss_settings = []
+    ssid = network[WifiEnums.SSID_KEY]
+    test.access_points[index].close()
+    time.sleep(5)
 
-        # Configure AP as required.
-        if "password" in network.keys():
-            password = network["password"]
-            security = hostapd_security.Security(
-                security_mode="wpa", password=password)
-        else:
-            security = hostapd_security.Security(security_mode=None, password=None)
-        config = hostapd_ap_preset.create_ap_preset(
-                                                    channel=channel,
-                                                    ssid=ssid,
-                                                    security=security,
-                                                    bss_settings=bss_settings,
-                                                    vht_bandwidth=bandwidth,
-                                                    profile_name='whirlwind',
-                                                    iface_wlan_2g=ap.wlan_2g,
-                                                    iface_wlan_5g=ap.wlan_5g)
-        ap.start_ap(config)
-        logging.info("AP started on channel {} with SSID {}".format(channel, ssid))
+    # Configure AP as required.
+    if "password" in network.keys():
+        password = network["password"]
+        security = hostapd_security.Security(security_mode="wpa",
+                                             password=password)
+    else:
+        security = hostapd_security.Security(security_mode=None, password=None)
+    config = hostapd_ap_preset.create_ap_preset(channel=channel,
+                                                ssid=ssid,
+                                                security=security,
+                                                bss_settings=bss_settings,
+                                                vht_bandwidth=bandwidth,
+                                                profile_name='whirlwind',
+                                                iface_wlan_2g=ap.wlan_2g,
+                                                iface_wlan_5g=ap.wlan_5g)
+    ap.start_ap(config)
+    logging.info("AP started on channel {} with SSID {}".format(channel, ssid))
 
 
 def turn_ap_off(test, AP):
@@ -2562,11 +2761,11 @@
         test: The test class object.
         AP: int, indicating which AP to turn OFF.
     """
-    hostapd_2g = test.access_points[AP-1]._aps['wlan0'].hostapd
+    hostapd_2g = test.access_points[AP - 1]._aps['wlan0'].hostapd
     if hostapd_2g.is_alive():
         hostapd_2g.stop()
         logging.debug('Turned WLAN0 AP%d off' % AP)
-    hostapd_5g = test.access_points[AP-1]._aps['wlan1'].hostapd
+    hostapd_5g = test.access_points[AP - 1]._aps['wlan1'].hostapd
     if hostapd_5g.is_alive():
         hostapd_5g.stop()
         logging.debug('Turned WLAN1 AP%d off' % AP)
@@ -2578,11 +2777,11 @@
         test: The test class object.
         AP: int, indicating which AP to turn ON.
     """
-    hostapd_2g = test.access_points[AP-1]._aps['wlan0'].hostapd
+    hostapd_2g = test.access_points[AP - 1]._aps['wlan0'].hostapd
     if not hostapd_2g.is_alive():
         hostapd_2g.start(hostapd_2g.config)
         logging.debug('Turned WLAN0 AP%d on' % AP)
-    hostapd_5g = test.access_points[AP-1]._aps['wlan1'].hostapd
+    hostapd_5g = test.access_points[AP - 1]._aps['wlan1'].hostapd
     if not hostapd_5g.is_alive():
         hostapd_5g.start(hostapd_5g.config)
         logging.debug('Turned WLAN1 AP%d on' % AP)
@@ -2606,14 +2805,129 @@
         channel: a wifi channel.
     """
     chan_switch_cmd = 'hostapd_cli -i {} chan_switch {} {}'
-    chan_switch_cmd_show = chan_switch_cmd.format(ap_iface,cs_count,channel)
+    chan_switch_cmd_show = chan_switch_cmd.format(ap_iface, cs_count, channel)
     dut.log.info('adb shell {}'.format(chan_switch_cmd_show))
-    chan_switch_result = dut.adb.shell(chan_switch_cmd.format(ap_iface,
-                                                              cs_count,
-                                                              channel))
+    chan_switch_result = dut.adb.shell(
+        chan_switch_cmd.format(ap_iface, cs_count, channel))
     if chan_switch_result == 'OK':
         dut.log.info('switch hotspot channel to {}'.format(channel))
         return chan_switch_result
 
     asserts.fail("Failed to switch hotspot channel")
 
+def get_wlan0_link(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 verify_11ax_wifi_connection(ad, wifi6_supported_models, wifi6_ap):
+    """Verify 11ax for wifi connection.
+
+    Args:
+      ad: adndroid device object
+      wifi6_supported_models: device supporting 11ax.
+      wifi6_ap: if the AP supports 11ax.
+    """
+    if wifi6_ap and ad.model in wifi6_supported_models:
+        logging.info("Verifying 11ax. Model: %s" % ad.model)
+        asserts.assert_true(
+            ad.droid.wifiGetConnectionStandard() ==
+            wifi_constants.WIFI_STANDARD_11AX, "DUT did not connect to 11ax.")
+
+def verify_11ax_softap(dut, dut_client, wifi6_supported_models):
+    """Verify 11ax SoftAp if devices support it.
+
+    Check if both DUT and DUT client supports 11ax, then SoftAp turns on
+    with 11ax mode and DUT client can connect to it.
+
+    Args:
+      dut: Softap device.
+      dut_client: Client connecting to softap.
+      wifi6_supported_models: List of device models supporting 11ax.
+    """
+    if dut.model in wifi6_supported_models and dut_client.model in wifi6_supported_models:
+        logging.info(
+            "Verifying 11ax softap. DUT model: %s, DUT Client model: %s",
+            dut.model, dut_client.model)
+        asserts.assert_true(
+            dut_client.droid.wifiGetConnectionStandard() ==
+            wifi_constants.WIFI_STANDARD_11AX,
+            "DUT failed to start SoftAp in 11ax.")
+
+def check_available_channels_in_bands_2_5(dut, country_code):
+    """Check if DUT is capable of enable BridgedAp.
+    #TODO: Find a way to make this function flexible by taking an argument.
+
+    Args:
+        country_code: country code, e.g., 'US', 'JP'.
+    Returns:
+        True: If DUT is capable of enable BridgedAp.
+        False: If DUT is not capable of enable BridgedAp.
+    """
+    set_wifi_country_code(dut, country_code)
+    country = dut.droid.wifiGetCountryCode()
+    dut.log.info("DUT current country code : {}".format(country))
+    # Wi-Fi ON and OFF to make sure country code take effet.
+    wifi_toggle_state(dut, True)
+    wifi_toggle_state(dut, False)
+
+    # Register SoftAp Callback and get SoftAp capability.
+    callbackId = dut.droid.registerSoftApCallback()
+    capability = get_current_softap_capability(dut, callbackId, True)
+    dut.droid.unregisterSoftApCallback(callbackId)
+
+    if capability[wifi_constants.
+                  SOFTAP_CAPABILITY_24GHZ_SUPPORTED_CHANNEL_LIST] and \
+        capability[wifi_constants.
+                   SOFTAP_CAPABILITY_5GHZ_SUPPORTED_CHANNEL_LIST]:
+        return True
+    return False
+
+
+@retry(tries=5, delay=2)
+def validate_ping_between_two_clients(dut1, dut2):
+    """Make 2 DUT ping each other.
+
+    Args:
+        dut1: An AndroidDevice object.
+        dut2: An AndroidDevice object.
+    """
+    # Get DUTs' IPv4 addresses.
+    dut1_ip = ""
+    dut2_ip = ""
+    try:
+        dut1_ip = dut1.droid.connectivityGetIPv4Addresses('wlan0')[0]
+    except IndexError as e:
+        dut1.log.info(
+            "{} has no Wi-Fi connection, cannot get IPv4 address."
+            .format(dut1.serial))
+    try:
+        dut2_ip = dut2.droid.connectivityGetIPv4Addresses('wlan0')[0]
+    except IndexError as e:
+        dut2.log.info(
+            "{} has no Wi-Fi connection, cannot get IPv4 address."
+            .format(dut2.serial))
+    # Test fail if not able to obtain two DUT's IPv4 addresses.
+    asserts.assert_true(dut1_ip and dut2_ip,
+                        "Ping failed because no DUT's IPv4 address")
+
+    dut1.log.info("{} IPv4 addresses : {}".format(dut1.serial, dut1_ip))
+    dut2.log.info("{} IPv4 addresses : {}".format(dut2.serial, dut2_ip))
+
+    # Two clients ping each other
+    dut1.log.info("{} ping {}".format(dut1_ip, dut2_ip))
+    asserts.assert_true(
+        utils.adb_shell_ping(dut1, count=10, dest_ip=dut2_ip,
+                             timeout=20),
+        "%s ping %s failed" % (dut1.serial, dut2_ip))
+
+    dut2.log.info("{} ping {}".format(dut2_ip, dut1_ip))
+    asserts.assert_true(
+        utils.adb_shell_ping(dut2, count=10, dest_ip=dut1_ip,
+                             timeout=20),
+        "%s ping %s failed" % (dut2.serial, dut1_ip))
+
diff --git a/acts_tests/acts_contrib/test_utils_tests/acts_import_test_utils_test.py b/acts_tests/acts_contrib/test_utils_tests/acts_import_test_utils_test.py
old mode 100644
new mode 100755
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/instrumentation_command_builder_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/instrumentation_command_builder_test.py
old mode 100644
new mode 100755
diff --git a/acts_tests/setup.py b/acts_tests/setup.py
index 166d869..b34ec8b 100755
--- a/acts_tests/setup.py
+++ b/acts_tests/setup.py
@@ -33,6 +33,7 @@
 install_requires = []
 
 
+
 def _setup_acts_framework(option, *args):
     """Locates and runs setup.py for the ACTS framework.
 
diff --git a/acts_tests/tests/OWNERS b/acts_tests/tests/OWNERS
index 38da00c..a0ea25e 100644
--- a/acts_tests/tests/OWNERS
+++ b/acts_tests/tests/OWNERS
@@ -20,6 +20,7 @@
 jerrypcchen@google.com
 martschneider@google.com
 timhuang@google.com
+sishichen@google.com
 
 # Fuchsia
 belgum@google.com
diff --git a/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py b/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py
index 6a5b861..4162b23 100644
--- a/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py
+++ b/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py
@@ -141,14 +141,14 @@
                     if adv_name in item
                 ]
                 devices_found.append(device[0][0].serial)
-                if len(device) is not 0:
+                if len(device) != 0:
                     address_list_tuple = (device[0][0], mac_address)
                 else:
                     continue
                 result = [item for item in address_list if mac_address in item]
                 # if length of result is 0, it indicates that we have discovered
                 # new mac address.
-                if len(result) is 0:
+                if len(result) == 0:
                     self.log.info("Found new mac address: {}".format(
                         address_list_tuple[1]))
                     address_list.append(address_list_tuple)
diff --git a/acts_tests/tests/google/fuchsia/flash/FlashTest.py b/acts_tests/tests/google/fuchsia/flash/FlashTest.py
index 0563f2a..572a431 100644
--- a/acts_tests/tests/google/fuchsia/flash/FlashTest.py
+++ b/acts_tests/tests/google/fuchsia/flash/FlashTest.py
@@ -43,10 +43,11 @@
     def teardown_class(self):
         try:
             dut = get_device(self.fuchsia_devices, 'DUT')
-            self.record_data(
-                {'sponge_properties': {
-                    'DUT_VERSION': dut.version()
-                }})
+            version = dut.version()
+            self.record_data({'sponge_properties': {
+                'DUT_VERSION': version,
+            }})
+            self.log.info("DUT version found: {}".format(version))
         except ValueError as err:
             self.log.warn("Failed to determine DUT: %s" % err)
         except DeviceOffline as err:
@@ -67,10 +68,11 @@
                     flash_counter = flash_retry_max
                 except Exception as err:
                     if fuchsia_device.device_pdu_config:
-                        self.log.info('Flashing failed.'
-                                      '  Hard rebooting fuchsia_device(%s)'
-                                      ' and retrying.' %
-                                      fuchsia_device.orig_ip)
+                        self.log.info(
+                            'Flashing failed with error: {}'.format(err))
+                        self.log.info('Hard rebooting fuchsia_device({}) and '
+                                      'retrying.'.format(
+                                          fuchsia_device.orig_ip))
                         fuchsia_device.reboot(reboot_type='hard',
                                               testbed_pdus=self.pdu_devices)
                         flash_counter = flash_counter + 1
diff --git a/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py b/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py
index e79d3f9..e640238 100644
--- a/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/performance/WlanRvrTest.py
@@ -213,6 +213,8 @@
                           'to Exception')
             self.log.info(e)
 
+        super().teardown_class()
+
     def on_fail(self, test_name, begin_time):
         super().on_fail(test_name, begin_time)
         self.cleanup_tests()
diff --git a/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py b/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py
index d1b0986..8eb6523 100644
--- a/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/performance/WlanWmmTest.py
@@ -196,6 +196,7 @@
     def teardown_class(self):
         for tc in self.wmm_transceivers:
             tc.destroy_resources()
+        super().teardown_class()
 
     def on_fail(self, test_name, begin_time):
         super().on_fail(test_name, begin_time)
diff --git a/acts_tests/tests/google/gnss/LabTtffTest.py b/acts_tests/tests/google/gnss/LabTtffTest.py
new file mode 100644
index 0000000..5c05fa9
--- /dev/null
+++ b/acts_tests/tests/google/gnss/LabTtffTest.py
@@ -0,0 +1,297 @@
+#!/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 os
+import time
+import glob
+import errno
+
+from acts import utils
+from acts import asserts
+from acts import signals
+from acts import base_test
+from pandas import DataFrame
+from collections import namedtuple
+from acts.controllers.spectracom_lib import gsg6
+from acts.test_utils.gnss import dut_log_test_utils as diaglog
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.gnss import gnss_testlog_utils as glogutils
+
+DEVICE_GPSLOG_FOLDER = '/sdcard/Android/data/com.android.gpstool/files/'
+GPS_PKG_NAME = 'com.android.gpstool'
+
+class LabTtffTest(base_test.BaseTestClass):
+
+    """ LAB TTFF Tests"""
+    GTW_GPSTOOL_APP = 'gtw_gpstool_apk'
+    SPECTRACOM_IP_KEY = 'spectracom_ip'
+    SPECTRACOM_PORT_KEY = 'spectracom_port'
+    SPECTRACOM_FILES_KEY = 'spectracom_files'
+    SPECTRACOM_POWER_KEY = 'spectracom_power_level'
+    SPIRENT_IP_KEY = 'spirent_ip'
+    SPIRENT_SCENARIO = 'sprient_scenario'
+    CUSTOM_FILES_KEY = 'custom_files'
+    CSTTFF_CRITERIA = 'cs_criteria'
+    HSTTFF_CRITERIA = 'hs_criteria'
+    WSTTFF_CRITERIA = 'ws_criteria'
+    CSTTFF_PECRITERIA = 'cs_ttff_pecriteria'
+    HSTTFF_PECRITERIA = 'hs_ttff_pecriteria'
+    WSTTFF_PECRITERIA = 'ws_ttff_pecriteria'
+    TTFF_ITERATION = 'ttff_iteration'
+    SIMULATOR_LOCATION = 'simulator_location'
+    DIAG_OPTION = 'diag_option'
+
+    def __init__(self, controllers):
+        """ Initializes class attributes. """
+
+        super().__init__(controllers)
+
+        self.dut = None
+        self.spectracom = None
+
+    def setup_class(self):
+        super().setup_class()
+
+        req_params = [
+            self.SPECTRACOM_IP_KEY, self.SPECTRACOM_PORT_KEY,
+            self.SPECTRACOM_FILES_KEY, self.SPECTRACOM_POWER_KEY,
+            self.CSTTFF_CRITERIA, self.HSTTFF_CRITERIA,
+            self.WSTTFF_CRITERIA, self.TTFF_ITERATION,
+            self.SIMULATOR_LOCATION, self.DIAG_OPTION
+        ]
+
+        for param in req_params:
+            if param not in self.user_params:
+                self.log.error('Required parameter {} is missing in config '
+                               'file.'.format(param))
+                raise signals.TestAbortClass(
+                    'Required parameter {} is missing in config '
+                               'file.'.format(param)) 
+        self.dut = self.android_devices[0]
+        self.spectracom_ip = self.user_params[self.SPECTRACOM_IP_KEY]
+        self.spectracom_port = self.user_params[self.SPECTRACOM_PORT_KEY]
+        self.spectracom_file = self.user_params[self.SPECTRACOM_FILES_KEY]
+        self.spectracom_power = self.user_params[self.SPECTRACOM_POWER_KEY]
+        self.gtw_gpstool_app = self.user_params[self.GTW_GPSTOOL_APP]
+        custom_files = self.user_params.get(self.CUSTOM_FILES_KEY, [])
+        self.cs_ttff_criteria = self.user_params.get(self.CSTTFF_CRITERIA, [])
+        self.hs_ttff_criteria = self.user_params.get(self.HSTTFF_CRITERIA, [])
+        self.ws_ttff_criteria = self.user_params.get(self.WSTTFF_CRITERIA, [])
+        self.cs_ttff_pecriteria = self.user_params.get(
+            self.CSTTFF_PECRITERIA, [])
+        self.hs_ttff_pecriteria = self.user_params.get(
+            self.HSTTFF_PECRITERIA, [])
+        self.ws_ttff_pecriteria = self.user_params.get(
+            self.WSTTFF_PECRITERIA, [])
+        self.ttff_iteration = self.user_params.get(self.TTFF_ITERATION, [])
+        self.simulator_location = self.user_params.get(
+            self.SIMULATOR_LOCATION, [])
+	self.diag_option = self.user_params.get(self.DIAG_OPTION, [])
+
+        test_type = namedtuple('Type', ['command', 'criteria'])
+        self.test_types = {
+            'cs': test_type('Cold Start', self.cs_ttff_criteria),
+            'ws': test_type('Warm Start', self.ws_ttff_criteria),
+            'hs': test_type('Hot Start', self.hs_ttff_criteria)
+        }
+
+        # Unpack the rockbottom script or fail class setup if it can't be found
+        for file in custom_files:
+            if 'rockbottom_' + self.dut.model in file:
+                self.rockbottom_script = file
+                break
+        else:
+            raise signals.TestAbortClass(
+                'Required rockbottom script is missing.')
+
+    def setup_test(self):
+
+	self.clear_gps_log()
+        self.spectracom = gsg6.GSG6(self.spectracom_ip, self.spectracom_port)
+
+        self.spectracom.stop_scenario()
+        time.sleep(10)
+        self.spectracom.close()
+
+        self.dut_rockbottom()
+        utils.set_location_service(self.dut, True)
+        gutils.reinstall_package_apk(self.dut, GPS_PKG_NAME,
+                                     self.gtw_gpstool_app)
+        self.spectracom = gsg6.GSG6(self.spectracom_ip, self.spectracom_port)
+        self.spectracom.connect()
+
+    def dut_rockbottom(self):
+        """
+        Set the dut to rockbottom state
+
+        """
+        # The rockbottom script might include a device reboot, so it is
+        # necessary to stop SL4A during its execution.
+        self.dut.stop_services()
+        self.log.info('Executing rockbottom script for ' + self.dut.model)
+        os.chmod(self.rockbottom_script, 0o777)
+        os.system('{} {}'.format(self.rockbottom_script, self.dut.serial))
+        # Make sure the DUT is in root mode after coming back
+        self.dut.root_adb()
+        # Restart SL4A
+        self.dut.start_services()
+
+    def teardown_class(self):
+        """ Executed after completing all selected test cases."""
+        self.clear_gps_log()
+        if self.spectracom:
+            self.spectracom.stop_scenario()
+            time.sleep(10)
+            self.spectracom.close()
+
+    def start_and_set_spectracom_power(self):
+        """
+        Start spectracom secnario and set power level.
+
+        """
+
+        self.spectracom.start_scenario(self.spectracom_file)
+        time.sleep(25)
+        self.spectracom.set_power(self.spectracom_power)
+
+    def get_and_verify_ttff(self, mode):
+        """Retrieve ttff with designate mode.
+
+            Args:
+                mode: A string for identify gnss test mode.
+        """
+        if mode not in self.test_types:
+            raise signals.TestError('Unrecognized mode %s' % mode)
+        test_type = self.test_types.get(mode)
+
+        gutils.process_gnss_by_gtw_gpstool(self.dut,
+                                           self.test_types['cs'].criteria)
+        begin_time = gutils.get_current_epoch_time()
+        gutils.start_ttff_by_gtw_gpstool(
+            self.dut, ttff_mode=mode,
+            iteration=self.ttff_iteration, aid_data=True)
+        ttff_data = gutils.process_ttff_by_gtw_gpstool(self.dut, begin_time,
+                                                       self.simulator_location)
+
+        gps_log_path = os.path.join(self.log_path, 'GPSLogs')
+        self.dut.adb.pull("{} {}".format(DEVICE_GPSLOG_FOLDER, gps_log_path))
+
+        gps_api_log = glob.glob(gps_log_path + '/GPS_API_*.txt')
+        ttff_loop_log = glob.glob(gps_log_path + '/GPS_{}_*.txt'.
+                                  format(mode.upper()))
+
+        if not gps_api_log and ttff_loop_log:
+            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
+                                    gps_log_path)
+
+        df = DataFrame(glogutils.parse_gpstool_ttfflog_to_df(gps_api_log[0]))
+
+        ttff_dict = {}
+        for i in ttff_data:
+            d = ttff_data[i]._asdict()
+            ttff_dict[i] = dict(d)
+
+        ttff_time =[]
+        ttff_pe = []
+        for i, k in ttff_dict.items():
+            ttff_time.append(ttff_dict[i]['ttff_time'])
+            ttff_pe.append(ttff_dict[i]['ttff_pe'])
+        df['ttff_time'] = ttff_time
+        df['ttff_pe'] = ttff_pe
+        df.to_json(gps_log_path + '/gps_log.json', orient='table')
+        result = gutils.check_ttff_data(
+            self.dut,
+            ttff_data,
+            ttff_mode=test_type.command,
+            criteria=test_type.criteria)
+        if not result:
+            raise signals.TestFailure('%s TTFF fails to reach '
+                                      'designated criteria'
+                                      % test_type.command)
+        return ttff_data
+
+    def verify_pe(self, mode):
+        """
+        Verify ttff Position Error with designate mode.
+
+        Args:
+             mode: A string for identify gnss test mode.
+        """
+
+        ffpe_type = namedtuple('Type', ['command', 'pecriteria'])
+        ffpe_types = {
+            'cs': ffpe_type('Cold Start', self.cs_ttff_pecriteria),
+            'ws': ffpe_type('Warm Start', self.ws_ttff_pecriteria),
+            'hs': ffpe_type('Hot Start', self.hs_ttff_pecriteria)
+        }
+
+        if mode not in self.test_types:
+            raise signals.TestError('Unrecognized mode %s' % mode)
+        test_type = self.test_types.get(mode)
+
+        ttff_data = self.get_and_verify_ttff(mode)
+        result = gutils.check_ttff_pe(
+            self.dut,
+            ttff_data,
+            ttff_mode=test_type.command,
+            pecriteria=test_type.pecriteria
+        )
+        if not result:
+            raise signals.TestFailure('%s TTFF fails to reach '
+                                      'designated criteria'
+                                      % test_type.command)
+        return ttff_data
+
+    def clear_gps_log(self):
+        """
+        Delete the existing GPS GTW Log from DUT.
+
+        """
+        self.dut.adb.shell("rm -rf {}".format(DEVICE_GPSLOG_FOLDER))
+
+    def test_gnss_cold_ttff_ffpe(self):
+
+        self.start_and_set_spectracom_power()
+        if self.diag_option is "QCOM":
+                diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+        else:
+                #start_tbdlog() yet to add for Broadcom
+                pass
+	self.verify_pe('cs')
+        diaglog.stop_background_diagmdlog(self.dut, self.qxdm_log_path, keep_logs=False)
+
+    def test_gnss_warm_ttff_ffpe(self):
+
+        self.start_and_set_spectracom_power()
+	if self.diag_option is "QCOM":
+	        diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+	else:
+		#start_tbdlog() yet to add for Broadcom
+		pass
+        self.verify_pe('ws')
+        diaglog.stop_background_diagmdlog(self.dut, self.qxdm_log_path, keep_logs=False)
+
+    def test_gnss_hot_ttff_ffpe(self):
+
+        self.start_and_set_spectracom_power()
+        if self.diag_option is "QCOM":
+                diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+        else:
+                #start_tbdlog() yet to add for Broadcom
+                pass
+        self.verify_pe('hs')
+        diaglog.stop_background_diagmdlog(self.dut, self.qxdm_log_path, keep_logs=False)
+
diff --git a/acts_tests/tests/google/net/CaptivePortalTest.py b/acts_tests/tests/google/net/CaptivePortalTest.py
index 60084f7..c44ff9b 100644
--- a/acts_tests/tests/google/net/CaptivePortalTest.py
+++ b/acts_tests/tests/google/net/CaptivePortalTest.py
@@ -29,6 +29,9 @@
 TIME_OUT = 20
 WLAN = "wlan0"
 ACCEPT_CONTINUE = "Accept and Continue"
+CONNECTED = "Connected"
+SIGN_IN_NOTIFICATION = "Sign in to network"
+FAS_FDQN = "netsplashpage.net"
 
 
 class CaptivePortalTest(WifiBaseTest):
@@ -58,7 +61,7 @@
             else:
                 self.configure_openwrt_ap_and_start(wpa_network=True)
                 self.wifi_network = self.openwrt.get_wifi_network()
-            self.openwrt.network_setting.setup_captive_portal()
+            self.openwrt.network_setting.setup_captive_portal(FAS_FDQN)
 
     def teardown_class(self):
         """Reset devices."""
@@ -90,7 +93,22 @@
         else:
             uutils.wait_and_click(self.dut, text="Internet")
 
-    def _verify_captive_portal(self, network, click_accept=ACCEPT_CONTINUE):
+    def _verify_sign_in_notification(self):
+        """Verify sign in notification shows for captive portal."""
+        curr_time = time.time()
+        while time.time() < curr_time + TIME_OUT:
+            time.sleep(3) # wait for sometime before checking the notification
+            screen_dump = uutils.get_screen_dump_xml(self.dut)
+            nodes = screen_dump.getElementsByTagName('node')
+            for node in nodes:
+                if SIGN_IN_NOTIFICATION in node.getAttribute(
+                    'text') or CONNECTED in node.getAttribute('text'):
+                  return
+        asserts.fail("Failed to get sign in notification")
+
+    def _verify_captive_portal(self, network, user="username",
+                               mail="user@example.net",
+                               click_accept=ACCEPT_CONTINUE):
         """Connect to captive portal network using uicd workflow.
 
         Steps:
@@ -100,25 +118,35 @@
 
         Args:
             network: captive portal network to connect to
+            user: Option for captive portal login in
+            mail: Option for captive portal login in
             click_accept: Notification to select to accept captive portal
         """
         # connect to captive portal wifi network
         wutils.connect_to_wifi_network(
             self.dut, network, check_connectivity=False)
-
+        # Wait for captive portal detection.
+        time.sleep(10)
         # run ui automator
+        self._verify_sign_in_notification()
         uutils.wait_and_click(self.dut, text="%s" % network["SSID"])
+        if uutils.has_element(self.dut, class_name="android.widget.EditText"):
+            uutils.wait_and_click(self.dut, class_name="android.widget.EditText")
+            self.dut.adb.shell("input text %s" % user)
+            self.dut.adb.shell("input keyevent 20")
+            self.dut.adb.shell("input text %s" % mail)
+            uutils.wait_and_click(self.dut, text="Accept Terms of Service")
         if uutils.has_element(self.dut, text="%s" % click_accept):
             uutils.wait_and_click(self.dut, text="%s" % click_accept)
 
         # wait for sometime for captive portal connection to go through
         curr_time = time.time()
         while time.time() < curr_time + TIME_OUT:
+            time.sleep(3) # wait for sometime for AP to send DHCP info
             link_prop = self.dut.droid.connectivityGetActiveLinkProperties()
             self.log.debug("Link properties %s" % link_prop)
             if link_prop and link_prop[IFACE] == WLAN:
                 break
-            time.sleep(2)
 
         # verify connectivity
         asserts.assert_true(
@@ -234,7 +262,7 @@
             3. Verify connectivity
         """
         cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
-        self.openwrt.network_setting.service_manager.restart("nodogsplash")
+        self.openwrt.network_setting.service_manager.restart("opennds")
         self._verify_captive_portal(self.wifi_network, click_accept="Continue")
 
     @test_tracker_info(uuid="1419e36d-0303-44ba-bc60-4d707b45ef48")
@@ -247,7 +275,7 @@
             3. Verify connectivity
         """
         cutils.set_private_dns(self.dut, cconst.PRIVATE_DNS_MODE_OFF)
-        self.openwrt.network_setting.service_manager.restart("nodogsplash")
+        self.openwrt.network_setting.service_manager.restart("opennds")
         self._verify_captive_portal(self.wifi_network, click_accept="Continue")
 
     @test_tracker_info(uuid="5aae44ee-fa62-47b9-9b3d-8121f9f92da1")
@@ -262,5 +290,7 @@
         cutils.set_private_dns(self.dut,
                                cconst.PRIVATE_DNS_MODE_STRICT,
                                cconst.DNS_GOOGLE_HOSTNAME)
-        self.openwrt.network_setting.service_manager.restart("nodogsplash")
+        self.openwrt.network_setting.service_manager.restart("opennds")
         self._verify_captive_portal(self.wifi_network, click_accept="Continue")
+
+
diff --git a/acts_tests/tests/google/net/DnsOverTlsTest.py b/acts_tests/tests/google/net/DnsOverTlsTest.py
index 0c3feac..f581e39 100644
--- a/acts_tests/tests/google/net/DnsOverTlsTest.py
+++ b/acts_tests/tests/google/net/DnsOverTlsTest.py
@@ -186,6 +186,42 @@
         # reset wifi
         wutils.reset_wifi(self.dut)
 
+    def _test_invalid_private_dns(self, net, dns_mode, dns_hostname):
+        """Test private DNS with invalid hostname, which should failed the ping.
+
+        :param net: Wi-Fi network to connect to
+        :param dns_mode: private DNS mode
+        :param dns_hostname: private DNS hostname
+        :return:
+        """
+
+        cutils.set_private_dns(self.dut, dns_mode, dns_hostname)
+        if net:
+            wutils.start_wifi_connection_scan_and_ensure_network_found(
+                self.dut, net[SSID])
+            wutils.wifi_connect(
+                self.dut, net, assert_on_fail=False, check_connectivity=False)
+
+        self._start_tcp_dump(self.dut)
+
+        # ping hosts should NOT pass
+        ping_result = False
+        for host in self.ping_hosts:
+            self.log.info("Pinging %s" % host)
+            try:
+                ping_result = self.dut.droid.httpPing(host)
+            except:
+                pass
+            # Ping result should keep negative with invalid DNS,
+            # so once it's positive we should break, and the test should fail
+            if ping_result:
+                break
+
+        pcap_file = self._stop_tcp_dump(self.dut)
+        self._verify_dns_queries_over_tls(pcap_file, True)
+        wutils.reset_wifi(self.dut)
+        return ping_result
+
     @test_tracker_info(uuid="2957e61c-d333-45fb-9ff9-2250c9c8535a")
     def test_private_dns_mode_off_wifi_ipv4_only_network(self):
         """Verify private dns mode off on ipv4 only network.
@@ -539,6 +575,7 @@
         for host in self.ping_hosts:
             wutils.validate_connection(self.dut, host)
 
+
         # stop tcpdump on device
         pcap_file = self._stop_tcp_dump(self.dut)
 
@@ -547,18 +584,17 @@
 
     @test_tracker_info(uuid="af6e34f1-3ad5-4ab0-b3b9-53008aa08294")
     def test_private_dns_mode_strict_invalid_hostnames(self):
-        """Verify that invalid hostnames are not saved for strict mode.
+        """Verify that invalid hostnames are not able to ping for strict mode.
 
         Steps:
             1. Set private DNS to strict mode with invalid hostname
             2. Verify that invalid hostname is not saved
         """
         invalid_hostnames = ["!%@&!*", "12093478129", "9.9.9.9", "sdkfjhasdf"]
-        for hostname in invalid_hostnames:
-            cutils.set_private_dns(
-                self.dut, cconst.PRIVATE_DNS_MODE_STRICT, hostname)
-            mode = self.dut.droid.getPrivateDnsMode()
-            specifier = self.dut.droid.getPrivateDnsSpecifier()
-            asserts.assert_true(
-                mode == cconst.PRIVATE_DNS_MODE_STRICT and specifier != hostname,
-                "Able to set invalid private DNS strict mode")
+        for dns_hostname in invalid_hostnames:
+            ping_result = self._test_invalid_private_dns(
+                self.get_wifi_network(False),
+                cconst.PRIVATE_DNS_MODE_STRICT,
+                dns_hostname)
+            asserts.assert_false(ping_result, "Ping success with invalid DNS.")
+
diff --git a/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py b/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py
index e6b0072..082f348 100644
--- a/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py
+++ b/acts_tests/tests/google/nr/cbr/CellBroadcastTest.py
@@ -20,9 +20,11 @@
 import xml.etree.ElementTree as ET
 import time
 import random
+import os
 
 from acts import signals
 from acts.logger import epoch_to_log_line_timestamp
+from acts.keys import Config
 from acts.test_decorators import test_tracker_info
 from acts.utils import load_config
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
@@ -35,15 +37,20 @@
 from acts_contrib.test_utils.tel.tel_defines import JAPAN_KDDI
 from acts_contrib.test_utils.tel.tel_defines import NEWZEALAND
 from acts_contrib.test_utils.tel.tel_defines import HONGKONG
-from acts_contrib.test_utils.tel.tel_defines import CHILE
-from acts_contrib.test_utils.tel.tel_defines import PERU
+from acts_contrib.test_utils.tel.tel_defines import CHILE_ENTEL
+from acts_contrib.test_utils.tel.tel_defines import CHILE_TELEFONICA
+from acts_contrib.test_utils.tel.tel_defines import MEXICO_TELEFONICA
+from acts_contrib.test_utils.tel.tel_defines import ELSALVADOR_TELEFONICA
+from acts_contrib.test_utils.tel.tel_defines import PERU_TELEFONICA
+from acts_contrib.test_utils.tel.tel_defines import PERU_ENTEL
 from acts_contrib.test_utils.tel.tel_defines import KOREA
 from acts_contrib.test_utils.tel.tel_defines import TAIWAN
 from acts_contrib.test_utils.tel.tel_defines import CANADA
 from acts_contrib.test_utils.tel.tel_defines import AUSTRALIA
 from acts_contrib.test_utils.tel.tel_defines import BRAZIL
 from acts_contrib.test_utils.tel.tel_defines import COLUMBIA
-from acts_contrib.test_utils.tel.tel_defines import ECUADOR
+from acts_contrib.test_utils.tel.tel_defines import ECUADOR_TELEFONICA
+from acts_contrib.test_utils.tel.tel_defines import ECUADOR_CLARO
 from acts_contrib.test_utils.tel.tel_defines import FRANCE
 from acts_contrib.test_utils.tel.tel_defines import PUERTORICO
 from acts_contrib.test_utils.tel.tel_defines import NETHERLANDS
@@ -64,6 +71,7 @@
 from acts_contrib.test_utils.tel.tel_defines import SAUDIARABIA
 from acts_contrib.test_utils.tel.tel_defines import MAIN_ACTIVITY
 from acts_contrib.test_utils.tel.tel_defines import CBR_PACKAGE
+from acts_contrib.test_utils.tel.tel_defines import SYSUI_PACKAGE
 from acts_contrib.test_utils.tel.tel_defines import CBR_ACTIVITY
 from acts_contrib.test_utils.tel.tel_defines import CBR_TEST_APK
 from acts_contrib.test_utils.tel.tel_defines import MCC_MNC
@@ -90,15 +98,42 @@
 class CellBroadcastTest(TelephonyBaseTest):
     def setup_class(self):
         super().setup_class()
-        self.region_plmn_list_conf = self.user_params.get("region_plmn_list")
-        self.emergency_alert_settings_conf = self.user_params.get("emergency_alert_settings")
-        self.emergency_alert_channels_conf = self.user_params.get("emergency_alert_channels")
+        req_param = ["region_plmn_list", "emergency_alert_settings", "emergency_alert_channels", "carrier_test_conf"]
+        self.unpack_userparams(req_param_names=req_param)
+        if hasattr(self, "region_plmn_list"):
+            if isinstance(self.region_plmn_list, list):
+                self.region_plmn_list = self.region_plmn_list[0]
+            if not os.path.isfile(self.region_plmn_list):
+                self.region_plmn_list = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.region_plmn_list)
+        if hasattr(self, "emergency_alert_settings"):
+            if isinstance(self.emergency_alert_settings, list):
+                self.emergency_alert_settings = self.emergency_alert_settings[0]
+            if not os.path.isfile(self.emergency_alert_settings):
+                self.emergency_alert_settings = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.emergency_alert_settings)
+        if hasattr(self, "emergency_alert_channels"):
+            if isinstance(self.emergency_alert_channels, list):
+                self.emergency_alert_channels = self.emergency_alert_channels[0]
+            if not os.path.isfile(self.emergency_alert_channels):
+                self.emergency_alert_channels = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.emergency_alert_channels)
+        if hasattr(self, "carrier_test_conf"):
+            if isinstance(self.carrier_test_conf, list):
+                self.carrier_test_conf = self.carrier_test_conf[0]
+            if not os.path.isfile(self.carrier_test_conf):
+                self.carrier_test_conf = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.carrier_test_conf)
         self.verify_vibration = self.user_params.get("verify_vibration", True)
         self._disable_vibration_check_for_11()
         self.verify_sound = self.user_params.get("verify_sound", True)
-        self.region_plmn_dict = load_config(self.region_plmn_list_conf)
-        self.emergency_alert_settings_dict = load_config(self.emergency_alert_settings_conf)
-        self.emergency_alert_channels_dict = load_config(self.emergency_alert_channels_conf)
+        self.region_plmn_dict = load_config(self.region_plmn_list)
+        self.emergency_alert_settings_dict = load_config(self.emergency_alert_settings)
+        self.emergency_alert_channels_dict = load_config(self.emergency_alert_channels)
         self._verify_cbr_test_apk_install(self.android_devices[0])
 
     def setup_test(self):
@@ -132,16 +167,30 @@
             self.verify_vibration = False
 
     def _get_toggle_value(self, ad, alert_text=None):
-        node = uutils.wait_and_get_xml_node(ad, timeout=30, text=alert_text)
+        if alert_text == "Alerts":
+            node = uutils.wait_and_get_xml_node(ad, timeout=30, matching_node=2, text=alert_text)
+        else:
+            node = uutils.wait_and_get_xml_node(ad, timeout=30, text=alert_text)
         return node.parentNode.nextSibling.firstChild.attributes['checked'].value
 
+    def _wait_and_click(self, ad, alert_text=None):
+        if alert_text == "Alerts":
+            uutils.wait_and_click(ad, text=alert_text, matching_node=2)
+        else:
+            uutils.wait_and_click(ad, text=alert_text)
+
+    def _has_element(self, ad, alert_text=None):
+        if alert_text == "Alerts":
+            return uutils.has_element(ad, text=alert_text, matching_node=2)
+        else:
+            return uutils.has_element(ad, text=alert_text)
 
     def _open_wea_settings_page(self, ad):
         ad.adb.shell("am start -a %s -n %s/%s" % (MAIN_ACTIVITY, CBR_PACKAGE, CBR_ACTIVITY))
 
 
     def _close_wea_settings_page(self, ad):
-        pid = ad.adb.shell("pidof %s" % CBR_PACKAGE)
+        pid = ad.adb.shell("pidof %s" % CBR_PACKAGE, ignore_status=True)
         ad.adb.shell("kill -9 %s" % pid, ignore_status=True)
 
 
@@ -158,15 +207,14 @@
                     region.upper(), mccmnc, imsi)
 
         # update carrier xml file
-        carrier_test_conf = self.user_params.get("carrier_test_conf")
-        tree = ET.parse(carrier_test_conf)
+        tree = ET.parse(self.carrier_test_conf)
         root = tree.getroot()
         root[1].attrib['value'] = mccmnc
         root[2].attrib['value'] = imsi
-        tree.write(carrier_test_conf)
+        tree.write(self.carrier_test_conf)
 
         # push carrier xml to device
-        ad.adb.push("%s %s" % (carrier_test_conf, CARRIER_TEST_CONF_XML_PATH))
+        ad.adb.push("%s %s" % (self.carrier_test_conf, CARRIER_TEST_CONF_XML_PATH))
 
         # reboot device
         reboot_device(ad)
@@ -185,10 +233,10 @@
             alert_value = value["default_value"]
             self._open_wea_settings_page(ad)
             # scroll till bottom
-            if not uutils.has_element(ad, text=alert_text):
+            if not self._has_element(ad, alert_text):
                 for _ in range(3):
                     ad.adb.shell(SCROLL_DOWN)
-                if not uutils.has_element(ad, text=alert_text):
+                if not self._has_element(ad, alert_text):
                     ad.log.error("UI - %s missing", alert_text)
                     result = False
                     continue
@@ -210,26 +258,26 @@
             alert_toggle = value["toggle_avail"]
             if alert_toggle == "true":
                 self._open_wea_settings_page(ad)
-                if not uutils.has_element(ad, text=alert_text):
+                if not self._has_element(ad, alert_text):
                     for _ in range(3):
                         ad.adb.shell(SCROLL_DOWN)
-                    if not uutils.has_element(ad, text=alert_text):
+                    if not self._has_element(ad, alert_text):
                         ad.log.error("UI - %s missing", alert_text)
                         result = False
                         continue
                 before_toggle = self._get_toggle_value(ad, alert_text)
-                uutils.wait_and_click(ad, text=alert_text)
+                self._wait_and_click(ad, alert_text)
                 after_toggle = self._get_toggle_value(ad, alert_text)
                 if before_toggle == after_toggle:
                     for _ in range(3):
                         ad.adb.shell(SCROLL_DOWN)
-                    uutils.wait_and_click(ad, text=alert_text)
+                    self._wait_and_click(ad, alert_text)
                     after_toggle = self._get_toggle_value(ad, alert_text)
                     if before_toggle == after_toggle:
                         ad.log.error("UI - fail to toggle %s", alert_text)
                         result = False
                 else:
-                    uutils.wait_and_click(ad, text=alert_text)
+                    self._wait_and_click(ad, alert_text)
                     reset_toggle = self._get_toggle_value(ad, alert_text)
                     if reset_toggle != before_toggle:
                         ad.log.error("UI - fail to reset toggle %s", alert_text)
@@ -293,10 +341,10 @@
         return False
 
 
-    def _verify_sound(self, ad, begintime, expectedtime, offset):
+    def _verify_sound(self, ad, begintime, expectedtime, offset, calling_package=CBR_PACKAGE):
         if not self.verify_sound:
             return True
-        cbr_pid = ad.adb.shell("pidof %s" % CBR_PACKAGE)
+        cbr_pid = ad.adb.shell("pidof %s" % calling_package)
         DUMPSYS_START_AUDIO = "dumpsys audio | grep %s | grep requestAudioFocus | tail -1" % cbr_pid
         DUMPSYS_END_AUDIO = "dumpsys audio | grep %s | grep abandonAudioFocus | tail -1" % cbr_pid
         start_audio = ad.adb.shell(DUMPSYS_START_AUDIO)
@@ -409,8 +457,19 @@
                     if self._popup_alert_in_statusbar_notifications(ad, alert_text):
                         ad.log.info("Found alert channel %d in status bar notifications, pop it up.", channel)
                         # Verify alert text in message.
-                        # Skip sound and vibration verification since it's not expected for notification alert.
                         alert_in_notification = self._verify_text_present_on_ui(ad, alert_text)
+                        if alert_in_notification:
+                            # Verify vibration and notification sound.
+                            # We check sound generated by com.android.systemui package.
+                            # For the reason of offset + 1, refer to b/199565843
+                            # TODO: The notification sound is initiated by system
+                            #  rather than CellBroadcastReceiver. In case there are
+                            #  any non-emergency notifications coming during testing, we
+                            #  should consider to validate notification id instead of
+                            #  com.android.systemui package. b/199565843
+                            if not (self._verify_vibration(ad, begintime, vibration_time, offset) and
+                                    self._verify_sound(ad, begintime, sound_time, offset+1, SYSUI_PACKAGE)):
+                                iteration_result = False
                 if alert_expected == "true" and not alert_in_notification:
                     iteration_result = False
                     self._log_and_screenshot_alert_fail(ad, "missing", region, channel)
@@ -450,6 +509,10 @@
             result = False
         log_screen_shot(ad, "default_settings_%s" % region)
         self._close_wea_settings_page(ad)
+        # Here close wea setting UI and then immediately open the UI that sometimes causes
+        # failing to open the wea setting UI. So we just delay 1 sec after closing
+        # the wea setting UI.
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
         if not self._verify_wea_toggle_settings(ad, region):
             log_screen_shot(ad, "toggle_settings_%s" % region)
             result = False
@@ -566,32 +629,92 @@
 
     @test_tracker_info(uuid="d9e2dca2-4965-48d5-9d79-352c4ccf9e0f")
     @TelephonyBaseTest.tel_test_wrap
-    def test_default_alert_settings_chile(self):
-        """ Verifies Wireless Emergency Alert settings for Chile
+    def test_default_alert_settings_chile_entel(self):
+        """ Verifies Wireless Emergency Alert settings for Chile_Entel
 
-        configures the device to Chile
+        configures the device to Chile_Entel
         verifies alert names and its default values
         toggles the alert twice if available
 
         Returns:
             True if pass; False if fail and collects screenshot
         """
-        return self._settings_test_flow(CHILE)
+        return self._settings_test_flow(CHILE_ENTEL)
+
+
+    @test_tracker_info(uuid="2a045a0e-145c-4677-b454-b0b63a69ea10")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_chile_telefonica(self):
+        """ Verifies Wireless Emergency Alert settings for Chile_Telefonica
+
+        configures the device to Chile_Telefonica
+        verifies alert names and its default values
+        toggles the alert twice if available
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._settings_test_flow(CHILE_TELEFONICA)
 
 
     @test_tracker_info(uuid="77cff297-fe3b-4b4c-b502-5324b4e91506")
     @TelephonyBaseTest.tel_test_wrap
-    def test_default_alert_settings_peru(self):
-        """ Verifies Wireless Emergency Alert settings for Peru
+    def test_default_alert_settings_peru_entel(self):
+        """ Verifies Wireless Emergency Alert settings for Peru_Entel
 
-        configures the device to Peru
+        configures the device to Peru_Entel
         verifies alert names and its default values
         toggles the alert twice if available
 
         Returns:
             True if pass; False if fail and collects screenshot
         """
-        return self._settings_test_flow(PERU)
+        return self._settings_test_flow(PERU_ENTEL)
+
+
+    @test_tracker_info(uuid="8b683505-288f-4587-95f2-9a8705476f09")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_peru_telefonica(self):
+        """ Verifies Wireless Emergency Alert settings for Peru_Telefonica
+
+        configures the device to Peru_Telefonica
+        verifies alert names and its default values
+        toggles the alert twice if available
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._settings_test_flow(PERU_TELEFONICA)
+
+
+    @test_tracker_info(uuid="cc0e0f64-2c77-4e20-b55e-6f555f7ecb97")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_elsalvador_telefonica(self):
+        """ Verifies Wireless Emergency Alert settings for Elsalvador_Telefonica
+
+        configures the device to Elsalvador_Telefonica
+        verifies alert names and its default values
+        toggles the alert twice if available
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._settings_test_flow(ELSALVADOR_TELEFONICA)
+
+
+    @test_tracker_info(uuid="339be9ef-7e0e-463a-ad45-12b7e74bb1c4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_mexico_telefonica(self):
+        """ Verifies Wireless Emergency Alert settings for Mexico_Telefonica
+
+        configures the device to Mexico_Telefonica
+        verifies alert names and its default values
+        toggles the alert twice if available
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._settings_test_flow(MEXICO_TELEFONICA)
 
 
     @test_tracker_info(uuid="4c3c4e65-c624-4eba-9a81-263f4ee01e12")
@@ -671,17 +794,32 @@
 
     @test_tracker_info(uuid="2ebfc05b-3512-4eff-9c09-5d8f49fe0b5e")
     @TelephonyBaseTest.tel_test_wrap
-    def test_default_alert_settings_ecuador(self):
-        """ Verifies Wireless Emergency Alert settings for Ecuador
+    def test_default_alert_settings_ecuador_telefonica(self):
+        """ Verifies Wireless Emergency Alert settings for Ecuador Telefonica
 
-        configures the device to Ecuador
+        configures the device to Ecuador Telefonica
         verifies alert names and its default values
         toggles the alert twice if available
 
         Returns:
             True if pass; False if fail and collects screenshot
         """
-        return self._settings_test_flow(ECUADOR)
+        return self._settings_test_flow(ECUADOR_TELEFONICA)
+
+
+    @test_tracker_info(uuid="694bf8f6-9e6e-46b4-98df-c7ab1a9a3ec8")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_default_alert_settings_ecuador_claro(self):
+        """ Verifies Wireless Emergency Alert settings for Ecuador Claro
+
+        configures the device to Ecuador Claro
+        verifies alert names and its default values
+        toggles the alert twice if available
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._settings_test_flow(ECUADOR_CLARO)
 
 
     @test_tracker_info(uuid="96628975-a23f-47f7-ab18-1aa7a7dc08b5")
@@ -992,10 +1130,10 @@
 
     @test_tracker_info(uuid="feea4e42-99cc-4075-bd78-15b149cb2e4c")
     @TelephonyBaseTest.tel_test_wrap
-    def test_send_receive_alerts_chile(self):
-        """ Verifies Wireless Emergency Alerts for CHILE
+    def test_send_receive_alerts_chile_entel(self):
+        """ Verifies Wireless Emergency Alerts for CHILE_ENTEL
 
-        configures the device to CHILE
+        configures the device to CHILE_ENTEL
         send alerts across all channels,
         verify if alert is received correctly
         verify sound and vibration timing
@@ -1004,7 +1142,24 @@
         Returns:
             True if pass; False if fail and collects screenshot
         """
-        return self._send_receive_test_flow(CHILE)
+        return self._send_receive_test_flow(CHILE_ENTEL)
+
+
+    @test_tracker_info(uuid="d2ec84ad-7f9a-4aa2-97e8-ca9ffa6c58a7")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_chile_telefonica(self):
+        """ Verifies Wireless Emergency Alerts for CHILE_TELEFONICA
+
+        configures the device to CHILE_TELEFONICA
+        send alerts across all channels,
+        verify if alert is received correctly
+        verify sound and vibration timing
+        click on OK/exit alert and verify text
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._send_receive_test_flow(CHILE_TELEFONICA)
 
 
     @test_tracker_info(uuid="4af30b94-50ea-4e19-8866-31fd3573a059")
@@ -1026,10 +1181,10 @@
 
     @test_tracker_info(uuid="2378b651-2097-48e6-b409-885bde9f4586")
     @TelephonyBaseTest.tel_test_wrap
-    def test_send_receive_alerts_ecuador(self):
-        """ Verifies Wireless Emergency Alerts for ECUADOR
+    def test_send_receive_alerts_ecuador_telefonica(self):
+        """ Verifies Wireless Emergency Alerts for ECUADOR Telefonica
 
-        configures the device to ECUADOR
+        configures the device to ECUADOR Telefonica
         send alerts across all channels,
         verify if alert is received correctly
         verify sound and vibration timing
@@ -1038,7 +1193,41 @@
         Returns:
             True if pass; False if fail and collects screenshot
         """
-        return self._send_receive_test_flow(ECUADOR)
+        return self._send_receive_test_flow(ECUADOR_TELEFONICA)
+
+
+    @test_tracker_info(uuid="cd064259-6cb2-460b-8225-de613f6cf967")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_ecuador_claro(self):
+        """ Verifies Wireless Emergency Alerts for ECUADOR Claro
+
+        configures the device to ECUADOR Claro
+        send alerts across all channels,
+        verify if alert is received correctly
+        verify sound and vibration timing
+        click on OK/exit alert and verify text
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._send_receive_test_flow(ECUADOR_CLARO)
+
+
+    @test_tracker_info(uuid="b11d1dd7-2090-463a-ba3a-39703db7f376")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_elsalvador_telefonica(self):
+        """ Verifies Wireless Emergency Alerts for ELSALVADOR telefonica
+
+        configures the device to ELSALVADOR telefonica
+        send alerts across all channels,
+        verify if alert is received correctly
+        verify sound and vibration timing
+        click on OK/exit alert and verify text
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._send_receive_test_flow(ELSALVADOR_TELEFONICA)
 
 
     @test_tracker_info(uuid="46d6c612-21df-476e-a41b-3baa621b52f0")
@@ -1228,6 +1417,23 @@
         return self._send_receive_test_flow(LITHUANIA)
 
 
+    @test_tracker_info(uuid="061cd0f3-cefa-4e5d-a1aa-f6125ccf9347")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_mexico_telefonica(self):
+        """ Verifies Wireless Emergency Alerts for MEXICO telefonica
+
+        configures the device to MEXICO telefonica
+        send alerts across all channels,
+        verify if alert is received correctly
+        verify sound and vibration timing
+        click on OK/exit alert and verify text
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._send_receive_test_flow(MEXICO_TELEFONICA)
+
+
     @test_tracker_info(uuid="a9c7cdbe-5a9e-49fb-af60-953e8c1547c0")
     @TelephonyBaseTest.tel_test_wrap
     def test_send_receive_alerts_netherlands(self):
@@ -1281,10 +1487,10 @@
 
     @test_tracker_info(uuid="35f0f156-1555-4bf1-98b1-b5848d8e2d39")
     @TelephonyBaseTest.tel_test_wrap
-    def test_send_receive_alerts_peru(self):
-        """ Verifies Wireless Emergency Alerts for PERU
+    def test_send_receive_alerts_peru_entel(self):
+        """ Verifies Wireless Emergency Alerts for PERU_ENTEL
 
-        configures the device to PERU
+        configures the device to PERU_ENTEL
         send alerts across all channels,
         verify if alert is received correctly
         verify sound and vibration timing
@@ -1293,7 +1499,24 @@
         Returns:
             True if pass; False if fail and collects screenshot
         """
-        return self._send_receive_test_flow(PERU)
+        return self._send_receive_test_flow(PERU_ENTEL)
+
+
+    @test_tracker_info(uuid="4708c783-ca89-498d-b74c-a6bc9df3fb32")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_send_receive_alerts_peru_telefonica(self):
+        """ Verifies Wireless Emergency Alerts for PERU_TELEFONICA
+
+        configures the device to PERU_TELEFONICA
+        send alerts across all channels,
+        verify if alert is received correctly
+        verify sound and vibration timing
+        click on OK/exit alert and verify text
+
+        Returns:
+            True if pass; False if fail and collects screenshot
+        """
+        return self._send_receive_test_flow(PERU_TELEFONICA)
 
 
     @test_tracker_info(uuid="fefb293a-5c22-45b2-9323-ccb355245c9a")
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py
index 01963e9..1dfefa9 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSDDSSwitchTest.py
@@ -15,7 +15,6 @@
 #   limitations under the License.
 
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
 from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.tel_dsds_utils import dds_switch_during_data_transfer_test
 from acts_contrib.test_utils.tel.tel_defines import YOUTUBE_PACKAGE_NAME
@@ -23,8 +22,6 @@
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 
-CallResult = TelephonyVoiceTestResult.CallResult.Value
-
 class Nsa5gDSDSDDSSwitchTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
@@ -43,6 +40,7 @@
     def test_dds_switch_youtube_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_volte", "5g_volte"])
 
@@ -51,6 +49,7 @@
     def test_dds_switch_youtube_and_voice_mo_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_volte", "5g_volte"],
             call_slot=0,
@@ -61,6 +60,7 @@
     def test_dds_switch_youtube_and_voice_mt_psim_5g_nsa_volte_esim_5g_nsa_volte(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_volte", "5g_volte"],
             call_slot=0,
@@ -71,6 +71,7 @@
     def test_dds_switch_youtube_and_voice_mo_esim_5g_nsa_volte_psim_5g_nsa_volte(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_volte", "5g_volte"],
             call_slot=1,
@@ -81,6 +82,7 @@
     def test_dds_switch_youtube_and_voice_mt_esim_5g_nsa_volte_psim_5g_nsa_volte(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_volte", "5g_volte"],
             call_slot=1,
@@ -91,6 +93,7 @@
     def test_dds_switch_youtube_and_voice_mo_psim_5g_nsa_csfb_esim_5g_nsa_csfb(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_csfb", "5g_csfb"],
             call_slot=0,
@@ -101,6 +104,7 @@
     def test_dds_switch_youtube_and_voice_mt_psim_5g_nsa_csfb_esim_5g_nsa_csfb(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_csfb", "5g_csfb"],
             call_slot=0,
@@ -111,6 +115,7 @@
     def test_dds_switch_youtube_and_voice_mo_esim_5g_nsa_csfb_psim_5g_nsa_csfb(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_csfb", "5g_csfb"],
             call_slot=1,
@@ -121,6 +126,7 @@
     def test_dds_switch_youtube_and_voice_mt_esim_5g_nsa_csfb_psim_5g_nsa_csfb(self):
         return dds_switch_during_data_transfer_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             nw_rat=["5g_csfb", "5g_csfb"],
             call_slot=1,
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py
index baefebf..488d098 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSSupplementaryServiceTest.py
@@ -14,13 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import re
-import time
-
-from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
 from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
@@ -32,8 +27,6 @@
 from acts_contrib.test_utils.tel.tel_dsds_utils import msim_call_forwarding
 from acts_contrib.test_utils.tel.tel_dsds_utils import msim_call_voice_conf
 
-CallResult = TelephonyVoiceTestResult.CallResult.Value
-
 class Nsa5gDSDSSupplementaryServiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
@@ -76,6 +69,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -105,6 +99,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -135,6 +130,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -164,6 +160,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -194,6 +191,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -223,6 +221,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -253,6 +252,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -282,6 +282,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -311,6 +312,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -340,6 +342,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -369,6 +372,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -398,6 +402,7 @@
         """
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -426,6 +431,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["5g_volte", "5g_volte"])
 
@@ -448,6 +454,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["5g_volte", "5g_volte"])
 
@@ -471,6 +478,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["5g_volte", "5g_volte"])
 
@@ -493,6 +501,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["5g_volte", "5g_volte"])
 
@@ -516,6 +525,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["5g_volte", "volte"])
 
@@ -538,6 +548,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["5g_volte", "volte"])
 
@@ -561,6 +572,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["5g_volte", "volte"])
 
@@ -583,6 +595,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["5g_volte", "volte"])
 
@@ -606,6 +619,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["volte", "5g_volte"])
 
@@ -628,6 +642,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["volte", "5g_volte"])
 
@@ -651,6 +666,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["volte", "5g_volte"])
 
@@ -673,6 +689,7 @@
         """
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["volte", "5g_volte"])
 
@@ -700,6 +717,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -710,6 +728,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -744,6 +763,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -754,6 +774,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -789,6 +810,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -799,6 +821,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -833,6 +856,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -843,6 +867,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -878,6 +903,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -888,6 +914,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -922,6 +949,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -932,6 +960,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -967,6 +996,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -977,6 +1007,7 @@
         	result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1011,6 +1042,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1021,6 +1053,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1056,6 +1089,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1067,6 +1101,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1101,6 +1136,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1112,6 +1148,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1147,6 +1184,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1158,6 +1196,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1192,6 +1231,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1203,6 +1243,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py
index 6921981..c684410 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSVoiceTest.py
@@ -16,6 +16,7 @@
 
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
@@ -26,6 +27,7 @@
 class Nsa5gDSDSVoiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
 
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
@@ -41,6 +43,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -58,6 +61,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -75,6 +79,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -92,6 +97,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -110,6 +116,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -127,6 +134,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -144,6 +152,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -161,6 +170,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -179,6 +189,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -196,6 +207,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -213,6 +225,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -230,6 +243,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -248,6 +262,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -265,6 +280,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -282,6 +298,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -299,6 +316,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -317,6 +335,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -334,6 +353,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -351,6 +371,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -368,6 +389,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -386,6 +408,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -403,6 +426,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -420,6 +444,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -437,6 +462,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -460,6 +486,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -483,6 +510,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -526,6 +554,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -549,6 +578,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -592,6 +622,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -615,6 +646,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -658,6 +690,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -681,6 +714,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -724,6 +758,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -747,6 +782,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -790,6 +826,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -813,6 +850,7 @@
         """
         return enable_slot_after_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -853,6 +891,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -877,6 +916,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -901,6 +941,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -925,6 +966,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -949,6 +991,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -973,6 +1016,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -997,6 +1041,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1021,6 +1066,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1045,6 +1091,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1069,6 +1116,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1093,6 +1141,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1117,6 +1166,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1141,6 +1191,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1165,6 +1216,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1189,6 +1241,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1213,6 +1266,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1237,6 +1291,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1261,6 +1316,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1285,6 +1341,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1309,6 +1366,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1333,6 +1391,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1357,6 +1416,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1381,6 +1441,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1405,6 +1466,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1430,6 +1492,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1457,6 +1520,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1484,6 +1548,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1511,6 +1576,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1538,6 +1604,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1565,6 +1632,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1592,6 +1660,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1619,6 +1688,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1646,6 +1716,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1673,6 +1744,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1700,6 +1772,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1727,6 +1800,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1754,6 +1828,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1781,6 +1856,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1808,6 +1884,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1835,6 +1912,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1862,6 +1940,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1889,6 +1968,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -1916,6 +1996,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -1943,6 +2024,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -1970,6 +2052,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -1997,6 +2080,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -2024,6 +2108,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -2051,6 +2136,7 @@
         """
         return dsds_voice_call_test(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
diff --git a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py
index 174e19b..a3f84fc 100644
--- a/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/nr/nsa5g/Nsa5gDSDSWfcSupplementaryServiceTest.py
@@ -16,7 +16,6 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
 from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
@@ -30,8 +29,6 @@
 from acts_contrib.test_utils.tel.tel_dsds_utils import msim_volte_wfc_call_forwarding
 from acts_contrib.test_utils.tel.tel_dsds_utils import msim_volte_wfc_call_voice_conf
 
-CallResult = TelephonyVoiceTestResult.CallResult.Value
-
 class Nsa5gDSDSWfcSupplementaryServiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
@@ -58,6 +55,7 @@
     def test_msim_cfu_psim_5g_nsa_wfc_wifi_preferred_apm_off_dds_0(self):
         return msim_volte_wfc_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             0,
@@ -71,6 +69,7 @@
     def test_msim_cfu_psim_5g_nsa_wfc_wifi_preferred_apm_off_dds_1(self):
         return msim_volte_wfc_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             1,
@@ -84,6 +83,7 @@
     def test_msim_cfu_psim_5g_nsa_volte_cellular_preferred_wifi_on_dds_0(self):
         return msim_volte_wfc_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             0,
@@ -97,6 +97,7 @@
     def test_msim_cfu_esim_5g_nsa_wfc_wifi_preferred_apm_off_dds_0(self):
         return msim_volte_wfc_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             0,
@@ -110,6 +111,7 @@
     def test_msim_cfu_esim_5g_nsa_wfc_wifi_preferred_apm_off_dds_1(self):
         return msim_volte_wfc_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             1,
@@ -123,6 +125,7 @@
     def test_msim_cfu_esim_5g_nsa_volte_cellular_preferred_wifi_on_dds_0(self):
         return msim_volte_wfc_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             0,
@@ -136,6 +139,7 @@
     def test_msim_cw_hold_swap_psim_5g_nsa_wfc_wifi_preferred_apm_off_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             0,
@@ -151,6 +155,7 @@
     def test_msim_cw_hold_swap_psim_5g_nsa_wfc_wifi_preferred_apm_off_dds_1(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             1,
@@ -166,6 +171,7 @@
     def test_msim_cw_hold_swap_psim_5g_nsa_volte_cellular_preferred_wifi_on_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             0,
@@ -182,6 +188,7 @@
     def test_msim_cw_hold_swap_esim_5g_nsa_wfc_wifi_preferred_apm_off_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             0,
@@ -197,6 +204,7 @@
     def test_msim_cw_hold_swap_esim_5g_nsa_wfc_wifi_preferred_apm_off_dds_1(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             1,
@@ -212,6 +220,7 @@
     def test_msim_cw_hold_swap_esim_5g_nsa_volte_cellular_preferred_wifi_on_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             0,
@@ -228,6 +237,7 @@
     def test_msim_conf_psim_5g_nsa_wfc_wifi_preferred_apm_off_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             0,
@@ -241,6 +251,7 @@
     def test_msim_conf_psim_5g_nsa_wfc_wifi_preferred_apm_off_dds_1(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             1,
@@ -254,6 +265,7 @@
     def test_msim_conf_psim_5g_nsa_volte_cellular_preferred_wifi_on_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             0,
@@ -267,6 +279,7 @@
     def test_msim_conf_esim_5g_wfc_wifi_preferred_apm_off_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             0,
@@ -280,6 +293,7 @@
     def test_msim_conf_esim_5g_nsa_wfc_wifi_preferred_apm_off_dds_1(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             1,
@@ -293,6 +307,7 @@
     def test_msim_conf_esim_5g_nsa_volte_cellular_preferred_wifi_on_dds_0(self):
         return msim_volte_wfc_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             0,
diff --git a/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py b/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py
index 14b36e3..726b274 100644
--- a/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py
+++ b/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py
@@ -20,7 +20,7 @@
 import time
 import os
 from acts import utils
-MDLOG_RUNNING_TIME = 300
+MDLOG_RUNNING_TIME = 1200
 DUT_ACTION_WAIT_TIME = 2
 
 class PowerGnssDpoSimTest(GBT.PowerGnssBaseTest):
@@ -42,28 +42,45 @@
     def test_gnss_dpoOFF_measurement(self):
         utils.set_location_service(self.dut, True)
         time.sleep(DUT_ACTION_WAIT_TIME)
-        gutil.start_gnss_by_gtw_gpstool(self.dut, state=True, type="gnss", bgdisplay=True)
-        self.dut.send_keycode("SLEEP")
+        self.dut.reboot()
+        gutil.write_modemconfig(self.dut, self.mdsapp,
+                                self.dpooff_nv_dict, self.modemparfile)
+        self.dut.reboot()
+        gutil.verify_modemconfig(self.dut, self.dpooff_nv_dict, self.modemparfile)
+        gutil.clear_aiding_data_by_gtw_gpstool(self.dut)
+        gutil.start_gnss_by_gtw_gpstool(self.dut, state=True, type="gnss",
+                                        bgdisplay=True)
         time.sleep(DUT_ACTION_WAIT_TIME)
-        self.measure_gnsspower_test_func()
         diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
         self.disconnect_usb(self.dut, MDLOG_RUNNING_TIME)
         qxdm_log_path = os.path.join(self.log_path, 'QXDM')
-        diaglog.stop_background_diagmdlog(self.dut, qxdm_log_path)
+        diaglog.stop_background_diagmdlog(self.dut, qxdm_log_path, keep_logs=False)
+        self.measure_gnsspower_test_func()
         gutil.start_gnss_by_gtw_gpstool(self.dut, state=False)
+        gps_log_path = os.path.join(self.log_path, 'GPS_LOGS')
+        diaglog.get_gpstool_logs(self.dut, gps_log_path, keep_logs=False)
 
     def test_gnss_dpoON_measurement(self):
         utils.set_location_service(self.dut, True)
         time.sleep(DUT_ACTION_WAIT_TIME)
-        gutil.start_gnss_by_gtw_gpstool(self.dut, state=True, type="gnss", bgdisplay=True)
-        self.dut.send_keycode("SLEEP")
+        self.dut.reboot()
+        gutil.write_modemconfig(self.dut, self.mdsapp,
+                                self.dpoon_nv_dict, self.modemparfile)
+        self.dut.reboot()
+        gutil.verify_modemconfig(self.dut, self.dpoon_nv_dict, self.modemparfile)
+        gutil.clear_aiding_data_by_gtw_gpstool(self.dut)
+        gutil.start_gnss_by_gtw_gpstool(self.dut, state=True,type="gnss",
+                                        bgdisplay=True)
         time.sleep(DUT_ACTION_WAIT_TIME)
-        self.measure_gnsspower_test_func()
         diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
         self.disconnect_usb(self.dut, MDLOG_RUNNING_TIME)
         qxdm_log_path = os.path.join(self.log_path, 'QXDM')
-        diaglog.stop_background_diagmdlog(self.dut, qxdm_log_path)
+        diaglog.stop_background_diagmdlog(self.dut, qxdm_log_path, keep_logs=False)
+
+        self.measure_gnsspower_test_func()
         gutil.start_gnss_by_gtw_gpstool(self.dut, state=False)
+        gps_log_path = os.path.join(self.log_path, 'GPS_LOGS')
+        diaglog.get_gpstool_logs(self.dut, gps_log_path, keep_logs=False)
 
     def test_gnss_rockbottom(self):
         self.dut.send_keycode("SLEEP")
diff --git a/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
index 4a711ce..2d0cd65 100644
--- a/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
@@ -87,7 +87,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
 from acts_contrib.test_utils.tel.tel_test_utils import test_data_browsing_success_using_sl4a
 from acts_contrib.test_utils.tel.tel_test_utils import test_data_browsing_failure_using_sl4a
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
index 2734fed..38df150 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
@@ -34,6 +34,7 @@
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify_for_subscription
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_default_data_sub_id
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
@@ -50,8 +51,6 @@
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_volte_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_wfc_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import \
-    sms_send_receive_verify_for_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
index fa6be7d..2352d40 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
@@ -30,9 +30,9 @@
 from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot
 from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_on_same_network_of_host_ad
 from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import sms_in_collision_send_receive_verify_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import sms_rx_power_off_multiple_send_receive_verify_for_subscription
-from acts_contrib.test_utils.tel.tel_test_utils import voice_call_in_collision_with_mt_sms_msim
+from acts_contrib.test_utils.tel.tel_message_utils import sms_in_collision_send_receive_verify_for_subscription
+from acts_contrib.test_utils.tel.tel_message_utils import sms_rx_power_off_multiple_send_receive_verify_for_subscription
+from acts_contrib.test_utils.tel.tel_message_utils import voice_call_in_collision_with_mt_sms_msim
 from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
 from acts_contrib.test_utils.tel.tel_test_utils import log_messaging_screen_shot
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
index 0684e43..7ac82f0 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
@@ -14,13 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import re
-import time
-
-from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
 from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
@@ -32,8 +27,6 @@
 from acts_contrib.test_utils.tel.tel_dsds_utils import msim_call_forwarding
 from acts_contrib.test_utils.tel.tel_dsds_utils import msim_call_voice_conf
 
-CallResult = TelephonyVoiceTestResult.CallResult.Value
-
 class TelLiveGFTDSDSSupplementaryServiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
@@ -59,6 +52,7 @@
     def test_msim_call_forwarding_unconditional_volte_psim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -72,6 +66,7 @@
     def test_msim_call_forwarding_unconditional_volte_psim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -85,6 +80,7 @@
     def test_msim_call_forwarding_unconditional_volte_esim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -98,6 +94,7 @@
     def test_msim_call_forwarding_unconditional_volte_esim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -113,6 +110,7 @@
     def test_msim_call_forwarding_unconditional_volte_csfb_psim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -126,6 +124,7 @@
     def test_msim_call_forwarding_unconditional_volte_csfb_psim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -139,6 +138,7 @@
     def test_msim_call_forwarding_unconditional_volte_csfb_esim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -152,6 +152,7 @@
     def test_msim_call_forwarding_unconditional_volte_csfb_esim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -160,13 +161,12 @@
             callee_rat=["volte", "csfb"],
             call_forwarding_type="unconditional")
 
-
-
     @test_tracker_info(uuid="29e36a21-9c94-418b-8628-e601e56fb168")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_call_forwarding_unconditional_csfb_volte_psim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -180,6 +180,7 @@
     def test_msim_call_forwarding_unconditional_csfb_volte_psim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -193,6 +194,7 @@
     def test_msim_call_forwarding_unconditional_csfb_volte_esim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -206,6 +208,7 @@
     def test_msim_call_forwarding_unconditional_csfb_volte_esim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -221,6 +224,7 @@
     def test_msim_call_forwarding_unconditional_csfb_psim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -234,6 +238,7 @@
     def test_msim_call_forwarding_unconditional_csfb_psim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             0,
@@ -247,6 +252,7 @@
     def test_msim_call_forwarding_unconditional_csfb_esim_dds_slot_0(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -260,6 +266,7 @@
     def test_msim_call_forwarding_unconditional_csfb_esim_dds_slot_1(self):
         return msim_call_forwarding(
             self.log,
+            self.tel_logger,
             self.android_devices,
             None,
             1,
@@ -268,13 +275,12 @@
             callee_rat=["csfb", "csfb"],
             call_forwarding_type="unconditional")
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="73ac948b-5260-44f1-a0a6-e4a410cb3283")
     def test_msim_voice_conf_call_host_volte_psim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["volte", "volte"])
 
@@ -283,6 +289,7 @@
     def test_msim_voice_conf_call_host_volte_psim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["volte", "volte"])
 
@@ -291,6 +298,7 @@
     def test_msim_voice_conf_call_host_volte_esim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["volte", "volte"])
 
@@ -299,16 +307,16 @@
     def test_msim_voice_conf_call_host_volte_esim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["volte", "volte"])
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="378f24cf-bb96-45e1-8150-02f08d7417b6")
     def test_msim_voice_conf_call_host_volte_csfb_psim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["volte", "csfb"])
 
@@ -317,6 +325,7 @@
     def test_msim_voice_conf_call_host_volte_csfb_psim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["volte", "csfb"])
 
@@ -325,6 +334,7 @@
     def test_msim_voice_conf_call_host_volte_csfb_esim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["volte", "csfb"])
 
@@ -333,16 +343,16 @@
     def test_msim_voice_conf_call_host_volte_csfb_esim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["volte", "csfb"])
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="90abbc8a-d492-45f9-9919-fae7e44c877a")
     def test_msim_voice_conf_call_host_csfb_volte_psim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["csfb", "volte"])
 
@@ -351,6 +361,7 @@
     def test_msim_voice_conf_call_host_csfb_volte_psim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["csfb", "volte"])
 
@@ -359,6 +370,7 @@
     def test_msim_voice_conf_call_host_csfb_volte_esim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["csfb", "volte"])
 
@@ -367,16 +379,16 @@
     def test_msim_voice_conf_call_host_csfb_volte_esim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["csfb", "volte"])
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="4831c07a-9a38-4ccd-8fa0-beaf52a2751e")
     def test_msim_voice_conf_call_host_csfb_psim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 0, host_rat=["csfb", "csfb"])
 
@@ -385,6 +397,7 @@
     def test_msim_voice_conf_call_host_csfb_psim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0, None, None, 1, host_rat=["csfb", "csfb"])
 
@@ -393,6 +406,7 @@
     def test_msim_voice_conf_call_host_csfb_esim_dds_slot_0(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 0, host_rat=["csfb", "csfb"])
 
@@ -401,17 +415,17 @@
     def test_msim_voice_conf_call_host_csfb_esim_dds_slot_1(self):
         return msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1, None, None, 1, host_rat=["csfb", "csfb"])
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="43e450c8-8a0b-4dfc-8c59-d0865c4c6399")
     def test_msim_call_waiting_volte_psim_dds_slot_0(self):
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -422,6 +436,7 @@
         	result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -439,6 +454,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -450,6 +466,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -467,6 +484,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -478,6 +496,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -495,6 +514,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -506,6 +526,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -517,14 +538,13 @@
             result = False
         return result
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="3cef5c80-b15f-45fa-8376-5252e61d7849")
     def test_msim_call_waiting_volte_csfb_psim_dds_slot_0(self):
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -536,6 +556,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -553,6 +574,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -564,6 +586,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -581,6 +604,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -592,6 +616,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -609,6 +634,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -620,6 +646,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -631,14 +658,13 @@
             result = False
         return result
 
-
-
     @TelephonyBaseTest.tel_test_wrap
     @test_tracker_info(uuid="b239d4be-9a36-4791-84df-ecebae645c84")
     def test_msim_call_waiting_csfb_volte_psim_dds_slot_0(self):
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -650,6 +676,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -667,6 +694,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -678,6 +706,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -695,6 +724,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -706,6 +736,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -723,6 +754,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -734,6 +766,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -751,6 +784,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -762,6 +796,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -779,6 +814,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -790,6 +826,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             0,
             None,
@@ -807,6 +844,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -818,6 +856,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -835,6 +874,7 @@
         result = True
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
@@ -846,6 +886,7 @@
             result = False
         if not msim_call_voice_conf(
             self.log,
+            self.tel_logger,
             self.android_devices,
             1,
             None,
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
index 9d0ace6..fc4feca 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
@@ -15,6 +15,7 @@
 #   limitations under the License.
 
 from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_dsds_utils import dsds_voice_call_test
@@ -22,6 +23,7 @@
 class TelLiveGFTDSDSVoiceTest(TelephonyBaseTest):
     def setup_class(self):
         TelephonyBaseTest.setup_class(self)
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
 
     def teardown_test(self):
         ensure_phones_idle(self.log, self.android_devices)
@@ -30,502 +32,502 @@
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["volte", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="7631b805-48b6-4b91-99a3-eef392e5b0fc")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["volte", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="4771e517-08cf-4169-afe7-fe3e41f05c45")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["volte", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="e8f914df-cada-4187-ab53-734624c9c941")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["volte", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="967a665a-9614-4fe4-b293-e20b66637802")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["volte", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="901c7fa3-039f-4888-90eb-82af587fa8dd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["volte", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="a78f2808-a6c6-4483-b7f5-ad1ec925dd52")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["volte", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="f6994dbd-c5a0-42c7-a43d-67227f5dfb88")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["volte", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="0786d7d3-d272-4233-83dd-0667e844094d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_csfb_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["volte", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="b9dfd46c-752c-4424-83b1-b5749a7018af")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_csfb_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["volte", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="8bc57654-a5d9-4c82-b11a-62e76ece9b43")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_csfb_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["volte", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="dbe44bf1-4638-4490-a06f-406205681ca5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_csfb_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["volte", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="ffd82db7-eaaa-4f96-9e3b-e0e15d054e62")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_csfb_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["volte", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="f7f3f28b-eecf-42e5-ba28-168a38337c80")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_csfb_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["volte", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="eb6ae70a-3251-4642-8268-b91b593cecfd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_csfb_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["volte", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="1d927140-34d2-4fc7-8fe4-b23a303fd190")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_csfb_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["volte", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="f15f6696-6e11-414b-8e28-9c16793b66b0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_volte_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["csfb", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="ca99d987-0bdb-4034-892f-cc0b1d22f381")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_volte_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["csfb", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="692bd3d0-05be-4597-afab-2f837a3f9bd4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_volte_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["csfb", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="87a5fae2-f32c-4b4d-8028-d065b582b117")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_volte_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["csfb", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="f6375034-5ecb-4872-bab2-cf9529f20fda")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_volte_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["csfb", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="6185bc28-1703-4ca2-a617-171d81adfe9a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_volte_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["csfb", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="06bad228-27af-47b4-9b74-aacba81f9da7")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_volte_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["csfb", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="5a5f2178-2ac6-4d21-bf6f-b9d8455365f1")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_volte_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["csfb", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="216f8569-8120-43c4-a9c5-da3081d168db")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_3g_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["volte", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="8d15524a-f7f9-4321-a962-b455bfdf4ec9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_3g_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["volte", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="c6aa5975-9ea6-4367-a59e-a248fde2c8be")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_3g_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["volte", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="a99a54e0-46ea-4d35-a3c1-d825c546cc21")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_3g_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["volte", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="6d128732-8a8e-488b-bb38-fbb764d228dd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_3g_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["volte", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="29517d00-5edf-4617-9d29-226d56426abf")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_volte_3g_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["volte", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="d18ec79e-3bc3-4c7e-89fd-d03519b2e2a6")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_3g_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["volte", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="6442b85a-b116-4987-b6d5-2d2b9bac7fd5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_volte_3g_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["volte", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="82e6f955-5156-4ad3-885d-d1d5ff0526cb")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_volte_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["3g", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="ffdafbac-026d-4d7d-a1dc-f639c01db818")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_volte_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["3g", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="b18dc6a7-e4a1-4409-a4aa-4e4add2fee13")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_volte_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["3g", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="ea6fc855-31b8-4680-b306-51228277e0d3")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_volte_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["3g", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="fdf9f4ea-a6f6-4434-a912-13711bb33a72")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_volte_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["3g", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="deb8e2f6-e097-451e-9f19-aadaf1820fea")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_volte_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["3g", "volte"], call_direction="mo")
 
     @test_tracker_info(uuid="6a636c5d-5da9-4916-9751-435ab39aaa00")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_volte_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["3g", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="0b9d9d5c-e5e7-4c9d-ab8a-0658fadbf450")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_volte_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["3g", "volte"], call_direction="mt")
 
     @test_tracker_info(uuid="fce99df9-8931-4a34-9285-121145fb9b2f")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["csfb", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="81d9a087-e494-40e4-a0fb-7e4ef62e566c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["csfb", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="d4520000-9cd1-4aff-9862-bfb6832d51ce")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["csfb", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="0eaf1e67-6aec-4a39-8f06-4e49009400e0")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["csfb", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="0e6bc15a-e510-4b56-826c-96e0add6b20a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["csfb", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="ecead288-424d-4579-bf6a-10a1a78600d5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["csfb", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="3a76076d-808e-45f8-b99c-95b82f2f07de")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["csfb", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="af638c75-c0e1-4ac1-83f5-bc0e6cdd913c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["csfb", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="0bf59f38-ddbc-4a88-bc8a-d6985e7d7567")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_3g_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["csfb", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="59f5a14a-c7e5-4bca-82dd-cb90b9a7a0e1")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_3g_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["csfb", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="79fc4d6f-0915-4717-80ae-db656cf3a82c")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_3g_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["csfb", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="b4927ebb-ae36-4ca7-a0b7-ea011b271122")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_3g_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["csfb", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="620be8d5-40b7-45f2-abfd-f788c8ce1977")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_3g_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["csfb", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="e277e0db-2dfb-4cfc-8d13-7c699f397b9b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_csfb_3g_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["csfb", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="f7822fca-a22d-4989-bca8-506e9652cee1")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_3g_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["csfb", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="60e5f5cd-a2e1-4a6a-b76b-25a8ce2b037d")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_csfb_3g_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["csfb", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="ef4b5c61-e9c9-4a29-8ff1-f9920ec9f4dd")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_csfb_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["3g", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="0e82934d-391d-46af-9609-f59522140ea9")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_csfb_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["3g", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="dab9ea2d-5370-4438-b0ee-67c68ebda024")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_csfb_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["3g", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="3ba5816e-11fe-4a39-968d-2e9853e8f47a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_csfb_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["3g", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="e8246c60-031d-4362-94c6-ad0882511d21")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_csfb_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["3g", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="db66ecb2-b09d-44be-991b-8025c6fab26a")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_csfb_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["3g", "csfb"], call_direction="mo")
 
     @test_tracker_info(uuid="008e990c-94e4-4adc-abaa-e328d84079a5")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_csfb_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["3g", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="0a87cbb1-96d9-4eed-b92c-745432dc4ba4")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_csfb_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["3g", "csfb"], call_direction="mt")
 
     @test_tracker_info(uuid="5620c3c8-e847-42c1-ae4e-b3370a0b6f98")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 0, mo_rat=["3g", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="a4415a1e-cd91-4a74-8f49-6c8ea428fe8f")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             0, None, 1, mo_rat=["3g", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="35a73981-15d7-491f-bade-42642cabbf76")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_psim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 0, mt_rat=["3g", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="e38de6bd-8f6b-4a95-8c0f-e685abc3e7ef")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_psim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 0, 1, mt_rat=["3g", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="1c86a1cb-5bd6-404a-a38f-4619a4b641a2")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 0, mo_rat=["3g", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="665736ff-206f-4c02-ae81-26f2e25d5988")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mo_3g_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             1, None, 1, mo_rat=["3g", "3g"], call_direction="mo")
 
     @test_tracker_info(uuid="5e5c8f33-60e5-44be-bf69-56c7715ead41")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_esim_dds_slot_0(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 0, mt_rat=["3g", "3g"], call_direction="mt")
 
     @test_tracker_info(uuid="2250b4d5-7b34-45cb-8ec2-300f4a4fbc2b")
     @TelephonyBaseTest.tel_test_wrap
     def test_msim_voice_call_mt_3g_esim_dds_slot_1(self):
         return dsds_voice_call_test(
-            self.log, self.android_devices,
+            self.log, self.tel_logger, self.android_devices,
             None, 1, 1, mt_rat=["3g", "3g"], call_direction="mt")
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py b/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
index 0319763..1d66adcc 100644
--- a/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
@@ -44,7 +44,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import is_voice_attached
 from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
 from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
diff --git a/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py b/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
index 4d90bdb..1e50457 100644
--- a/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
@@ -50,7 +50,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import power_off_sim
 from acts_contrib.test_utils.tel.tel_test_utils import power_on_sim
 from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
 from acts_contrib.test_utils.tel.tel_test_utils import trigger_modem_crash
 from acts_contrib.test_utils.tel.tel_test_utils import trigger_modem_crash_by_modem
diff --git a/acts_tests/tests/google/tel/live/TelLiveRilImsKpiTest.py b/acts_tests/tests/google/tel/live/TelLiveRilImsKpiTest.py
new file mode 100644
index 0000000..20a93bb
--- /dev/null
+++ b/acts_tests/tests/google/tel/live/TelLiveRilImsKpiTest.py
@@ -0,0 +1,1354 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2021 - Google
+#
+#   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 datetime import datetime
+
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_data_utils import airplane_mode_test
+from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WIFI_CONNECTION
+from acts_contrib.test_utils.tel.tel_logging_utils import start_pixellogger_always_on_logging
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_voice_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_all_sub_id
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte_for_subscription
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
+from acts_contrib.test_utils.tel.tel_parse_utils import print_nested_dict
+from acts_contrib.test_utils.tel.tel_parse_utils import parse_ims_reg
+from acts_contrib.test_utils.tel.tel_parse_utils import ON_IMS_MM_TEL_CONNECTED_4G_SLOT0
+from acts_contrib.test_utils.tel.tel_parse_utils import ON_IMS_MM_TEL_CONNECTED_4G_SLOT1
+from acts_contrib.test_utils.tel.tel_parse_utils import ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0
+from acts_contrib.test_utils.tel.tel_parse_utils import ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1
+from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_test_utils import check_is_wifi_connected
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_service
+from acts_contrib.test_utils.tel.tel_test_utils import wait_for_log
+from acts.utils import get_current_epoch_time
+
+SETUP_PHONE_FAIL = 'SETUP_PHONE_FAIL'
+VERIFY_NETWORK_FAIL = 'VERIFY_NETWORK_FAIL'
+VERIFY_INTERNET_FAIL = 'VERIFY_INTERNET_FAIL'
+TOGGLE_OFF_APM_FAIL = 'TOGGLE_OFF_APM_FAIL'
+
+CALCULATE_EVERY_N_CYCLES = 10
+
+def test_result(result_list, cycle, min_fail=0, failrate=0):
+    failure_count = len(list(filter(lambda x: (x != True), result_list)))
+    if failure_count >= min_fail:
+        if failure_count >= cycle * failrate:
+            return False
+    return True
+
+def wait_for_wifi_disconnected(ad, wifi_ssid):
+    """Wait until Wifi is disconnected.
+
+    Args:
+        ad: Android object
+        wifi_ssid: to specify the Wifi AP which should be disconnected.
+
+    Returns:
+        True if Wifi is disconnected before time-out. Otherwise False.
+    """
+    wait_time = 0
+    while wait_time < MAX_WAIT_TIME_WIFI_CONNECTION:
+        if check_is_wifi_connected(ad.log, ad, wifi_ssid):
+            ad.droid.wifiToggleState(False)
+            time.sleep(3)
+            wait_time = wait_time + 3
+        else:
+            ad.log.info('Wifi is disconnected.')
+            return True
+
+    if check_is_wifi_connected(ad.log, ad, wifi_ssid):
+        ad.log.error('Wifi still is connected to %s.', wifi_ssid)
+        return False
+    else:
+        ad.log.info('Wifi is disconnected.')
+        return True
+
+class TelLiveRilImsKpiTest(TelephonyBaseTest):
+    def setup_class(self):
+        TelephonyBaseTest.setup_class(self)
+        start_pixellogger_always_on_logging(self.android_devices[0])
+        self.tel_logger = TelephonyMetricLogger.for_test_case()
+        self.user_params["telephony_auto_rerun"] = 0
+        self.reboot_4g_test_cycle = self.user_params.get(
+            'reboot_4g_test_cycle', 1)
+        self.reboot_iwlan_test_cycle = self.user_params.get(
+            'reboot_iwlan_test_cycle', 1)
+        self.cycle_apm_4g_test_cycle = self.user_params.get(
+            'cycle_apm_4g_test_cycle', 1)
+        self.cycle_wifi_in_apm_mode_test_cycle = self.user_params.get(
+            'cycle_wifi_in_apm_mode_test_cycle', 1)
+        self.ims_handover_4g_to_iwlan_with_voice_call_wfc_wifi_preferred_test_cycle = self.user_params.get(
+            'ims_handover_4g_to_iwlan_with_voice_call_wfc_wifi_preferred_test_cycle', 1)
+        self.ims_handover_4g_to_iwlan_wfc_wifi_preferred_test_cycle = self.user_params.get(
+            'ims_handover_4g_to_iwlan_wfc_wifi_preferred_test_cycle', 1)
+        self.ims_handover_iwlan_to_4g_wfc_wifi_preferred_test_cycle = self.user_params.get(
+            'ims_handover_iwlan_to_4g_wfc_wifi_preferred_test_cycle', 1)
+        self.ims_handover_iwlan_to_4g_with_voice_call_wfc_wifi_preferred_test_cycle = self.user_params.get(
+            'ims_handover_iwlan_to_4g_with_voice_call_wfc_wifi_preferred_test_cycle', 1)
+        self.ims_handover_iwlan_to_4g_wfc_cellular_preferred_test_cycle = self.user_params.get(
+            'ims_handover_iwlan_to_4g_wfc_cellular_preferred_test_cycle', 1)
+        self.ims_handover_iwlan_to_4g_with_voice_call_wfc_cellular_preferred_test_cycle = self.user_params.get(
+            'ims_handover_iwlan_to_4g_with_voice_call_wfc_cellular_preferred_test_cycle', 1)
+
+    def teardown_test(self):
+        for ad in self.android_devices:
+            toggle_airplane_mode(self.log, ad, False)
+
+    @test_tracker_info(uuid="d6a59a3c-2bbc-4ed3-a41e-4492b4ab8a50")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_reboot_4g(self):
+        """Reboot UE and measure bootup IMS registration time on LTE.
+
+        Test steps:
+            1. Enable VoLTE at all slots and ensure IMS is registered over LTE
+                cellular network at all slots.
+            2. Reboot UE.
+            3. Parse logcat to calculate IMS registration time on LTE after
+                bootup.
+        """
+        ad = self.android_devices[0]
+        cycle = self.reboot_4g_test_cycle
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        if getattr(ad, 'dsds', False):
+            the_other_slot = 1 - voice_slot
+        else:
+            the_other_slot = None
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+        for attempt in range(cycle):
+            _continue = True
+            self.log.info(
+                '==================> Reboot on LTE %s/%s <==================',
+                attempt+1,
+                cycle)
+
+            sub_id_list = get_all_sub_id(ad)
+            for sub_id in sub_id_list:
+                if not phone_setup_volte_for_subscription(self.log, ad, sub_id):
+                    result.append(SETUP_PHONE_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not wait_for_network_service(self.log, ad):
+                    result.append(VERIFY_NETWORK_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                begin_time = datetime.now()
+                if reboot_test(self.log, ad):
+                    result.append(True)
+                else:
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS bootup registration at slot %s '
+                    '======',
+                    voice_slot)
+                ad.log.info(result)
+
+                for slot in [voice_slot, the_other_slot]:
+                    if slot is None:
+                        continue
+
+                    ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                        ad, search_intervals, '4g', 'reboot', slot=slot)
+                    ad.log.info(
+                        '====== IMS bootup registration at slot %s ======', slot)
+                    for msg in ims_reg:
+                        print_nested_dict(ad, msg)
+
+                    ad.log.info(
+                        '====== Attempt of parsing fail at slot %s ======' % slot)
+                    for msg in parsing_fail:
+                        ad.log.info(msg)
+
+                    ad.log.warning('====== Summary ======')
+                    ad.log.warning(
+                        '%s/%s cycles failed.',
+                        (len(result) - result.count(True)),
+                        len(result))
+                    for attempt, value in enumerate(result):
+                        if value is not True:
+                            ad.log.warning('Cycle %s: %s', attempt+1, value)
+                    try:
+                        fail_rate = (
+                            len(result) - result.count(True))/len(result)
+                        ad.log.info(
+                            'Fail rate of IMS bootup registration at slot %s: %s',
+                            slot,
+                            fail_rate)
+                    except Exception as e:
+                        ad.log.error(
+                            'Fail rate of IMS bootup registration at slot %s: '
+                            'ERROR (%s)',
+                            slot,
+                            e)
+
+                    ad.log.info(
+                        'Number of trials with valid parsed logs: %s',
+                        len(ims_reg))
+                    ad.log.info(
+                        'Average IMS bootup registration time at slot %s: %s',
+                        slot,
+                        avg_ims_reg_duration)
+
+            if exit_due_to_high_fail_rate:
+                break
+
+        return test_result(result, cycle)
+
+    @test_tracker_info(uuid="c97dd2f2-9e8a-43d4-9352-b53abe5ac6a4")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_reboot_iwlan(self):
+        """Reboot UE and measure bootup IMS registration time over iwlan.
+
+        Test steps:
+            1. Enable VoLTE at all slots; enable WFC and set WFC mode to
+                Wi-Fi-preferred mode; connect Wi-Fi and ensure IMS is registered
+                at all slots over iwlan.
+            2. Reboot UE.
+            3. Parse logcat to calculate IMS registration time over iwlan after
+                bootup.
+        """
+        ad = self.android_devices[0]
+        cycle = self.reboot_iwlan_test_cycle
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        if getattr(ad, 'dsds', False):
+            the_other_slot = 1 - voice_slot
+        else:
+            the_other_slot = None
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+        for attempt in range(cycle):
+            _continue = True
+            self.log.info(
+                '==================> Reboot on iwlan %s/%s <==================',
+                attempt+1,
+                cycle)
+
+            sub_id_list = get_all_sub_id(ad)
+            for sub_id in sub_id_list:
+                if not phone_setup_iwlan_for_subscription(
+                    self.log,
+                    ad,
+                    sub_id,
+                    False,
+                    WFC_MODE_WIFI_PREFERRED,
+                    self.wifi_network_ssid,
+                    self.wifi_network_pass):
+
+                    result.append(SETUP_PHONE_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+                    wait_for_wifi_disconnected(ad, self.wifi_network_ssid)
+
+            if _continue:
+                if not verify_internet_connection(self.log, ad):
+                    result.append(VERIFY_INTERNET_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                begin_time = datetime.now()
+                if reboot_test(self.log, ad, wifi_ssid=self.wifi_network_ssid):
+                    result.append(True)
+                else:
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS bootup registration at slot %s '
+                    '======',
+                    voice_slot)
+                ad.log.info(result)
+
+                for slot in [voice_slot, the_other_slot]:
+                    if slot is None:
+                        continue
+
+                    ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                        ad, search_intervals, 'iwlan', 'reboot', slot=slot)
+                    ad.log.info(
+                        '====== IMS bootup registration at slot %s ======', slot)
+                    for msg in ims_reg:
+                        print_nested_dict(ad, msg)
+
+                    ad.log.info(
+                        '====== Attempt of parsing fail at slot %s ======' % slot)
+                    for msg in parsing_fail:
+                        ad.log.info(msg)
+
+                    ad.log.warning('====== Summary ======')
+                    ad.log.warning(
+                        '%s/%s cycles failed.',
+                        (len(result) - result.count(True)),
+                        len(result))
+                    for attempt, value in enumerate(result):
+                        if value is not True:
+                            ad.log.warning('Cycle %s: %s', attempt+1, value)
+
+                    try:
+                        fail_rate = (
+                            len(result) - result.count(True))/len(result)
+                        ad.log.info(
+                            'Fail rate of IMS bootup registration at slot %s: %s',
+                            slot,
+                            fail_rate)
+                    except Exception as e:
+                        ad.log.error(
+                            'Fail rate of IMS bootup registration at slot %s: '
+                            'ERROR (%s)',
+                            slot,
+                            e)
+
+                    ad.log.info(
+                        'Number of trials with valid parsed logs: %s',
+                        len(ims_reg))
+                    ad.log.info(
+                        'Average IMS bootup registration time at slot %s: %s',
+                        slot, avg_ims_reg_duration)
+            if exit_due_to_high_fail_rate:
+                break
+
+        return test_result(result, cycle)
+
+    @test_tracker_info(uuid="45ed4572-7de9-4e1b-b2ec-58dea722fa3e")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_cycle_airplane_mode_4g(self):
+        """Cycle airplane mode and measure IMS registration time on LTE
+
+        Test steps:
+            1. Enable VoLTE at all slots and ensure IMS is registered on LTE at
+                all slots.
+            2. Cycle airplane mode.
+            3. Parse logcat to calculate IMS registration time right after
+                recovery of cellular service.
+        """
+        ad = self.android_devices[0]
+        cycle = self.cycle_apm_4g_test_cycle
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        if getattr(ad, 'dsds', False):
+            the_other_slot = 1 - voice_slot
+        else:
+            the_other_slot = None
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+        for attempt in range(cycle):
+            _continue = True
+            self.log.info(
+                '============> Cycle airplane mode on LTE %s/%s <============',
+                attempt+1,
+                cycle)
+
+            sub_id_list = get_all_sub_id(ad)
+            for sub_id in sub_id_list:
+                if not phone_setup_volte_for_subscription(self.log, ad, sub_id):
+                    result.append(SETUP_PHONE_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not wait_for_network_service(self.log, ad):
+                    result.append(VERIFY_NETWORK_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                begin_time = datetime.now()
+                if airplane_mode_test(self.log, ad):
+                    result.append(True)
+                else:
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS registration at slot %s ======',
+                    voice_slot)
+                ad.log.info(result)
+
+                for slot in [voice_slot, the_other_slot]:
+                    if slot is None:
+                        continue
+
+                    ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                        ad, search_intervals, '4g', 'apm', slot=slot)
+                    ad.log.info(
+                        '====== IMS registration at slot %s ======', slot)
+                    for msg in ims_reg:
+                        print_nested_dict(ad, msg)
+
+                    ad.log.info(
+                        '====== Attempt of parsing fail at slot %s ======' % slot)
+                    for msg in parsing_fail:
+                        ad.log.info(msg)
+
+                    ad.log.warning('====== Summary ======')
+                    ad.log.warning('%s/%s cycles failed.', (len(result) - result.count(True)), len(result))
+                    for attempt, value in enumerate(result):
+                        if value is not True:
+                            ad.log.warning('Cycle %s: %s', attempt+1, value)
+
+                    try:
+                        fail_rate = (
+                            len(result) - result.count(True))/len(result)
+                        ad.log.info(
+                            'Fail rate of IMS registration at slot %s: %s',
+                            slot,
+                            fail_rate)
+                    except Exception as e:
+                        ad.log.error(
+                            'Fail rate of IMS registration at slot %s: '
+                            'ERROR (%s)',
+                            slot,
+                            e)
+
+                    ad.log.info(
+                        'Number of trials with valid parsed logs: %s',
+                        len(ims_reg))
+                    ad.log.info(
+                        'Average IMS registration time at slot %s: %s',
+                        slot, avg_ims_reg_duration)
+
+            if exit_due_to_high_fail_rate:
+                break
+
+        return test_result(result, cycle)
+
+    @test_tracker_info(uuid="915c9403-8bbc-45c7-be53-8b0de4191716")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_cycle_wifi_in_apm_mode(self):
+        """Cycle Wi-Fi in airplane mode and measure IMS registration time over
+            iwlan.
+
+        Test steps:
+            1. Enable VoLTE; enable WFC and set WFC mode to Wi-Fi-preferred mode;
+                turn on airplane mode and connect Wi-Fi to ensure IMS is
+                registered over iwlan.
+            2. Cycle Wi-Fi.
+            3. Parse logcat to calculate IMS registration time right after
+                recovery of Wi-Fi connection in airplane mode.
+        """
+        ad = self.android_devices[0]
+        cycle = self.cycle_wifi_in_apm_mode_test_cycle
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+        for attempt in range(cycle):
+            _continue = True
+            self.log.info(
+                '============> Cycle WiFi in airplane mode %s/%s <============',
+                attempt+1,
+                cycle)
+
+            begin_time = datetime.now()
+
+            if not wait_for_wifi_disconnected(ad, self.wifi_network_ssid):
+                result.append(False)
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+                _continue = False
+                if not test_result(result, cycle, 10, 0.1):
+                    exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not phone_setup_iwlan(
+                    self.log,
+                    ad,
+                    True,
+                    WFC_MODE_WIFI_PREFERRED,
+                    self.wifi_network_ssid,
+                    self.wifi_network_pass):
+
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not verify_internet_connection(self.log, ad):
+                    result.append(VERIFY_INTERNET_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not wait_for_wifi_disconnected(
+                    ad, self.wifi_network_ssid):
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                result.append(True)
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS registration at slot %s ======',
+                    voice_slot)
+                ad.log.info(result)
+
+                ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                    ad, search_intervals, 'iwlan', 'apm')
+                ad.log.info(
+                    '====== IMS registration at slot %s ======', voice_slot)
+                for msg in ims_reg:
+                    ad.log.info(msg)
+
+                ad.log.info(
+                    '====== Attempt of parsing fail at slot %s ======' % voice_slot)
+                for msg in parsing_fail:
+                    ad.log.info(msg)
+
+                ad.log.warning('====== Summary ======')
+                ad.log.warning(
+                    '%s/%s cycles failed.',
+                    (len(result) - result.count(True)),
+                    len(result))
+                for attempt, value in enumerate(result):
+                    if value is not True:
+                        ad.log.warning('Cycle %s: %s', attempt+1, value)
+
+                try:
+                    fail_rate = (len(result) - result.count(True))/len(result)
+                    ad.log.info(
+                        'Fail rate of IMS registration at slot %s: %s',
+                        voice_slot,
+                        fail_rate)
+                except Exception as e:
+                    ad.log.error(
+                        'Fail rate of IMS registration at slot %s: ERROR (%s)',
+                        voice_slot,
+                        e)
+
+                ad.log.info(
+                    'Number of trials with valid parsed logs: %s', len(ims_reg))
+                ad.log.info(
+                    'Average IMS registration time at slot %s: %s',
+                    voice_slot, avg_ims_reg_duration)
+
+            if exit_due_to_high_fail_rate:
+                break
+        toggle_airplane_mode(self.log, ad, False)
+        return test_result(result, cycle)
+
+    def ims_handover_4g_to_iwlan_wfc_wifi_preferred(self, voice_call=False):
+        """Connect WFC to make IMS registration hand over from LTE to iwlan in
+            Wi-Fi-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC and set WFC mode to Wi-Fi-preferred mode.
+            2. Ensure Wi-Fi are disconnected and all cellular services are
+                available.
+            3. (Optional) Make a VoLTE call and keep the call active.
+            4. Connect Wi-Fi. The IMS registration should hand over from LTE
+                to iwlan.
+            5. Parse logcat to calculate the IMS handover time.
+
+        Args:
+            voice_call: True if an active VoLTE call is desired in the background
+                during IMS handover procedure. Otherwise False.
+        """
+        ad = self.android_devices[0]
+        if voice_call:
+            cycle = self.ims_handover_4g_to_iwlan_with_voice_call_wfc_wifi_preferred_test_cycle
+        else:
+            cycle = self.ims_handover_4g_to_iwlan_wfc_wifi_preferred_test_cycle
+
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+
+        if not set_wfc_mode(self.log, ad, WFC_MODE_WIFI_PREFERRED):
+            return False
+
+        for attempt in range(cycle):
+            _continue = True
+            self.log.info(
+                '======> IMS handover from LTE to iwlan in WFC wifi-preferred '
+                'mode %s/%s <======',
+                attempt+1,
+                cycle)
+
+            begin_time = datetime.now()
+
+            if not wait_for_wifi_disconnected(ad, self.wifi_network_ssid):
+                result.append(False)
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+                _continue = False
+                if not test_result(result, cycle, 10, 0.1):
+                    exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not wait_for_network_service(
+                    self.log,
+                    ad,
+                    wifi_connected=False,
+                    ims_reg=True):
+
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_call:
+                    ad_mt = self.android_devices[1]
+                    call_params = [(
+                        ad,
+                        ad_mt,
+                        None,
+                        is_phone_in_call_volte,
+                        None)]
+                    call_result = two_phone_call_short_seq(
+                        self.log,
+                        ad,
+                        phone_idle_volte,
+                        is_phone_in_call_volte,
+                        ad_mt,
+                        None,
+                        None,
+                        wait_time_in_call=30,
+                        call_params=call_params)
+                    self.tel_logger.set_result(call_result.result_value)
+                    if not call_result:
+                        self._take_bug_report(
+                            self.test_name, begin_time=get_current_epoch_time())
+                        _continue = False
+                        if not test_result(result, cycle, 10, 0.1):
+                            exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not phone_setup_iwlan(
+                    self.log,
+                    ad,
+                    False,
+                    WFC_MODE_WIFI_PREFERRED,
+                    self.wifi_network_ssid,
+                    self.wifi_network_pass):
+
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_slot == 0:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0
+                else:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1
+
+                if wait_for_log(ad, ims_pattern, begin_time=begin_time):
+                    ad.log.info(
+                        'IMS registration is handed over from LTE to iwlan.')
+                else:
+                    ad.log.error(
+                        'IMS registration is NOT yet handed over from LTE to '
+                        'iwlan.')
+
+            if voice_call:
+                hangup_call(self.log, ad)
+
+            if _continue:
+                if not verify_internet_connection(self.log, ad):
+                    result.append(VERIFY_INTERNET_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not wait_for_wifi_disconnected(
+                    ad, self.wifi_network_ssid):
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_slot == 0:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_4G_SLOT0
+                else:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_4G_SLOT1
+
+                if wait_for_log(ad, ims_pattern, begin_time=begin_time):
+                    ad.log.info(
+                        'IMS registration is handed over from iwlan to LTE.')
+                else:
+                    ad.log.error(
+                        'IMS registration is NOT yet handed over from iwlan to '
+                        'LTE.')
+
+            if _continue:
+                result.append(True)
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS registration at slot %s ======',
+                    voice_slot)
+                ad.log.info(result)
+
+                ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                    ad, search_intervals, 'iwlan', 'apm')
+                ad.log.info(
+                    '====== IMS registration at slot %s ======', voice_slot)
+                for msg in ims_reg:
+                    ad.log.info(msg)
+
+                ad.log.info(
+                    '====== Attempt of parsing fail at slot %s ======' % voice_slot)
+                for msg in parsing_fail:
+                    ad.log.info(msg)
+
+                ad.log.warning('====== Summary ======')
+                ad.log.warning(
+                    '%s/%s cycles failed.',
+                    (len(result) - result.count(True)),
+                    len(result))
+                for attempt, value in enumerate(result):
+                    if value is not True:
+                        ad.log.warning('Cycle %s: %s', attempt+1, value)
+
+                try:
+                    fail_rate = (len(result) - result.count(True))/len(result)
+                    ad.log.info(
+                        'Fail rate of IMS registration at slot %s: %s',
+                        voice_slot,
+                        fail_rate)
+                except Exception as e:
+                    ad.log.error(
+                        'Fail rate of IMS registration at slot %s: ERROR (%s)',
+                        voice_slot,
+                        e)
+
+                ad.log.info(
+                    'Number of trials with valid parsed logs: %s',len(ims_reg))
+                ad.log.info(
+                    'Average IMS registration time at slot %s: %s',
+                    voice_slot, avg_ims_reg_duration)
+
+            if exit_due_to_high_fail_rate:
+                break
+
+        return test_result(result, cycle)
+
+    @test_tracker_info(uuid="e3d1aaa8-f673-4a2b-adb1-cfa525a4edbd")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ims_handover_4g_to_iwlan_with_voice_call_wfc_wifi_preferred(self):
+        """Connect WFC to make IMS registration hand over from LTE to iwlan in
+            Wi-Fi-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC and set WFC mode to Wi-Fi-preferred mode.
+            2. Ensure Wi-Fi are disconnected and all cellular services are
+                available.
+            3. Make a VoLTE call and keep the call active.
+            4. Connect Wi-Fi. The IMS registration should hand over from LTE
+                to iwlan.
+            5. Parse logcat to calculate the IMS handover time.
+        """
+        return self.ims_handover_4g_to_iwlan_wfc_wifi_preferred(True)
+
+    @test_tracker_info(uuid="bd86fb46-04bd-4642-923a-747e6c9d4282")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ims_handover_4g_to_iwlan_wfc_wifi_preferred(self):
+        """Connect WFC to make IMS registration hand over from LTE to iwlan in
+            Wi-Fi-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC and set WFC mode to Wi-Fi-preferred mode.
+            2. Ensure Wi-Fi are disconnected and all cellular services are
+                available.
+            3. Connect Wi-Fi. The IMS registration should hand over from LTE
+                to iwlan.
+            4. Parse logcat to calculate the IMS handover time.
+        """
+        return self.ims_handover_4g_to_iwlan_wfc_wifi_preferred(False)
+
+    def ims_handover_iwlan_to_4g_wfc_wifi_preferred(self, voice_call=False):
+        """Disconnect Wi-Fi to make IMS registration hand over from iwlan to LTE
+            in Wi-Fi-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC, set WFC mode to Wi-Fi-preferred mode, and then
+                connect Wi-Fi to let IMS register over iwlan.
+            2. (Optional) Make a WFC call and keep the call active.
+            3. Disconnect Wi-Fi. The IMS registration should hand over from iwlan
+                to LTE.
+            4. Parse logcat to calculate the IMS handover time.
+
+        Args:
+            voice_call: True if an active WFC call is desired in the background
+                during IMS handover procedure. Otherwise False.
+        """
+        ad = self.android_devices[0]
+        if voice_call:
+            cycle = self.ims_handover_iwlan_to_4g_with_voice_call_wfc_wifi_preferred_test_cycle
+        else:
+            cycle = self.ims_handover_iwlan_to_4g_wfc_wifi_preferred_test_cycle
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+        for attempt in range(cycle):
+            _continue = True
+            self.log.info(
+                '======> IMS handover from iwlan to LTE in WFC wifi-preferred '
+                'mode %s/%s <======',
+                attempt+1,
+                cycle)
+
+            begin_time = datetime.now()
+
+            if not phone_setup_iwlan(
+                self.log,
+                ad,
+                False,
+                WFC_MODE_WIFI_PREFERRED,
+                self.wifi_network_ssid,
+                self.wifi_network_pass):
+
+                result.append(False)
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+                _continue = False
+                if not test_result(result, cycle, 10, 0.1):
+                    exit_due_to_high_fail_rate = True
+
+                wait_for_wifi_disconnected(ad, self.wifi_network_ssid)
+
+            if _continue:
+                if voice_slot == 0:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0
+                else:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1
+
+                if wait_for_log(ad, ims_pattern, begin_time=begin_time):
+                    ad.log.info(
+                        'IMS registration is handed over from LTE to iwlan.')
+                else:
+                    ad.log.error(
+                        'IMS registration is NOT yet handed over from LTE to '
+                        'iwlan.')
+
+            if _continue:
+                if not verify_internet_connection(self.log, ad):
+                    result.append(VERIFY_INTERNET_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_call:
+                    ad_mt = self.android_devices[1]
+                    call_params = [(
+                        ad,
+                        ad_mt,
+                        None,
+                        is_phone_in_call_iwlan,
+                        None)]
+                    call_result = two_phone_call_short_seq(
+                        self.log,
+                        ad,
+                        phone_idle_iwlan,
+                        is_phone_in_call_iwlan,
+                        ad_mt,
+                        None,
+                        None,
+                        wait_time_in_call=30,
+                        call_params=call_params)
+                    self.tel_logger.set_result(call_result.result_value)
+                    if not call_result:
+                        self._take_bug_report(
+                            self.test_name, begin_time=get_current_epoch_time())
+                        _continue = False
+                        if not test_result(result, cycle, 10, 0.1):
+                            exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not wait_for_wifi_disconnected(
+                    ad, self.wifi_network_ssid):
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_slot == 0:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_4G_SLOT0
+                else:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_4G_SLOT1
+
+                if wait_for_log(ad, ims_pattern, begin_time=begin_time):
+                    ad.log.info(
+                        'IMS registration is handed over from iwlan to LTE.')
+                else:
+                    ad.log.error(
+                        'IMS registration is NOT yet handed over from iwlan to '
+                        'LTE.')
+
+            if voice_call:
+                hangup_call(self.log, ad)
+
+            if _continue:
+                if not wait_for_network_service(
+                    self.log,
+                    ad,
+                    wifi_connected=False,
+                    ims_reg=True):
+
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                result.append(True)
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS registration at slot %s ======',
+                    voice_slot)
+                ad.log.info(result)
+
+                ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                    ad, search_intervals, '4g', 'wifi_off')
+                ad.log.info(
+                    '====== IMS registration at slot %s ======', voice_slot)
+                for msg in ims_reg:
+                    ad.log.info(msg)
+
+                ad.log.info(
+                    '====== Attempt of parsing fail at slot %s ======' % voice_slot)
+                for msg in parsing_fail:
+                    ad.log.info(msg)
+
+                ad.log.warning('====== Summary ======')
+                ad.log.warning(
+                    '%s/%s cycles failed.',
+                    (len(result) - result.count(True)),
+                    len(result))
+                for attempt, value in enumerate(result):
+                    if value is not True:
+                        ad.log.warning('Cycle %s: %s', attempt+1, value)
+
+                try:
+                    fail_rate = (len(result) - result.count(True))/len(result)
+                    ad.log.info(
+                        'Fail rate of IMS registration at slot %s: %s',
+                        voice_slot,
+                        fail_rate)
+                except Exception as e:
+                    ad.log.error(
+                        'Fail rate of IMS registration at slot %s: ERROR (%s)',
+                        voice_slot,
+                        e)
+
+                ad.log.info(
+                    'Number of trials with valid parsed logs: %s', len(ims_reg))
+                ad.log.info(
+                    'Average IMS registration time at slot %s: %s',
+                    voice_slot, avg_ims_reg_duration)
+
+            if exit_due_to_high_fail_rate:
+                break
+
+        return test_result(result, cycle)
+
+    @test_tracker_info(uuid="6ce623a6-7ef9-42db-8099-d5c449e70bff")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ims_handover_iwlan_to_4g_wfc_wifi_preferred(self):
+        """Disconnect Wi-Fi to make IMS registration hand over from iwlan to LTE
+            in Wi-Fi-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC, set WFC mode to Wi-Fi-preferred mode, and then
+                connect Wi-Fi to let IMS register over iwlan.
+            2. Disconnect Wi-Fi. The IMS registration should hand over from iwlan
+                to LTE.
+            3. Parse logcat to calculate the IMS handover time.
+        """
+        return self.ims_handover_iwlan_to_4g_wfc_wifi_preferred(False)
+
+    @test_tracker_info(uuid="b965ab09-d8b1-423f-bb98-2cdd43babbe3")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ims_handover_iwlan_to_4g_with_voice_call_wfc_wifi_preferred(self):
+        """Disconnect Wi-Fi to make IMS registration hand over from iwlan to LTE
+            in Wi-Fi-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC, set WFC mode to Wi-Fi-preferred mode, and then
+                connect Wi-Fi to let IMS register over iwlan.
+            2. Make a WFC call and keep the call active.
+            3. Disconnect Wi-Fi. The IMS registration should hand over from iwlan
+                to LTE.
+            4. Parse logcat to calculate the IMS handover time.
+        """
+        return self.ims_handover_iwlan_to_4g_wfc_wifi_preferred(True)
+
+    def ims_handover_iwlan_to_4g_wfc_cellular_preferred(self, voice_call=False):
+        """Turn off airplane mode to make IMS registration hand over from iwlan to LTE
+            in WFC cellular-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC, set WFC mode to cellular-preferred mode, turn on
+                airplane mode and then connect Wi-Fi to let IMS register over
+                iwlan.
+            2. (Optional) Make a WFC call and keep the call active.
+            3. Turn off airplane mode. The IMS registration should hand over
+                from iwlan to LTE.
+            4. Parse logcat to calculate the IMS handover time.
+
+        Args:
+            voice_call: True if an active WFC call is desired in the background
+                during IMS handover procedure. Otherwise False.
+        """
+        ad = self.android_devices[0]
+        if voice_call:
+            cycle = self.ims_handover_iwlan_to_4g_with_voice_call_wfc_cellular_preferred_test_cycle
+        else:
+            cycle = self.ims_handover_iwlan_to_4g_wfc_cellular_preferred_test_cycle
+
+        voice_slot = get_slot_index_from_voice_sub_id(ad)
+
+        result = []
+        search_intervals = []
+        exit_due_to_high_fail_rate = False
+        for attempt in range(cycle):
+            _continue = True
+
+            self.log.info(
+                '======> IMS handover from iwlan to LTE in WFC '
+                'cellular-preferred mode %s/%s <======',
+                attempt+1,
+                cycle)
+
+            begin_time = datetime.now()
+
+            if not phone_setup_iwlan(
+                self.log,
+                ad,
+                True,
+                WFC_MODE_CELLULAR_PREFERRED,
+                self.wifi_network_ssid,
+                self.wifi_network_pass):
+
+                result.append(False)
+                self._take_bug_report(
+                    self.test_name, begin_time=get_current_epoch_time())
+                _continue = False
+                if not test_result(result, cycle, 10, 0.1):
+                    exit_due_to_high_fail_rate = True
+
+                toggle_airplane_mode(self.log, ad, False)
+
+            if _continue:
+                if voice_slot == 0:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0
+                else:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1
+
+                if wait_for_log(ad, ims_pattern, begin_time=begin_time):
+                    ad.log.info(
+                        'IMS registration is handed over from LTE to iwlan.')
+                else:
+                    ad.log.error(
+                        'IMS registration is NOT yet handed over from LTE to '
+                        'iwlan.')
+
+            if _continue:
+                if not verify_internet_connection(self.log, ad):
+                    result.append(VERIFY_INTERNET_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_call:
+                    ad_mt = self.android_devices[1]
+                    call_params = [(
+                        ad,
+                        ad_mt,
+                        None,
+                        is_phone_in_call_iwlan,
+                        None)]
+                    call_result = two_phone_call_short_seq(
+                        self.log,
+                        ad,
+                        phone_idle_iwlan,
+                        is_phone_in_call_iwlan,
+                        ad_mt,
+                        None,
+                        None,
+                        wait_time_in_call=30,
+                        call_params=call_params)
+                    self.tel_logger.set_result(call_result.result_value)
+                    if not call_result:
+                        self._take_bug_report(
+                            self.test_name, begin_time=get_current_epoch_time())
+                        _continue = False
+                        if not test_result(result, cycle, 10, 0.1):
+                            exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if not toggle_airplane_mode(self.log, ad, False):
+                    result.append(TOGGLE_OFF_APM_FAIL)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                if voice_slot == 0:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_4G_SLOT0
+                else:
+                    ims_pattern = ON_IMS_MM_TEL_CONNECTED_4G_SLOT1
+
+                if wait_for_log(ad, ims_pattern, begin_time=begin_time):
+                    ad.log.info(
+                        'IMS registration is handed over from iwlan to LTE.')
+                else:
+                    ad.log.error(
+                        'IMS registration is NOT yet handed over from iwlan to '
+                        'LTE.')
+
+            if voice_call:
+                hangup_call(self.log, ad)
+
+            if _continue:
+                if not wait_for_network_service(
+                    self.log,
+                    ad,
+                    wifi_connected=True,
+                    wifi_ssid=self.wifi_network_ssid,
+                    ims_reg=True):
+
+                    result.append(False)
+                    self._take_bug_report(
+                        self.test_name, begin_time=get_current_epoch_time())
+                    _continue = False
+                    if not test_result(result, cycle, 10, 0.1):
+                        exit_due_to_high_fail_rate = True
+
+            if _continue:
+                result.append(True)
+                end_time = datetime.now()
+                search_intervals.append([begin_time, end_time])
+
+            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
+                attempt == cycle - 1) or exit_due_to_high_fail_rate:
+
+                ad.log.info(
+                    '====== Test result of IMS registration at slot %s ======',
+                    voice_slot)
+                ad.log.info(result)
+
+                ims_reg, parsing_fail, avg_ims_reg_duration = parse_ims_reg(
+                    ad, search_intervals, '4g', 'apm')
+                ad.log.info(
+                    '====== IMS registration at slot %s ======', voice_slot)
+                for msg in ims_reg:
+                    ad.log.info(msg)
+
+                ad.log.info(
+                    '====== Attempt of parsing fail at slot %s ======' % voice_slot)
+                for msg in parsing_fail:
+                    ad.log.info(msg)
+
+                ad.log.warning('====== Summary ======')
+                ad.log.warning(
+                    '%s/%s cycles failed.',
+                    (len(result) - result.count(True)),
+                    len(result))
+                for attempt, value in enumerate(result):
+                    if value is not True:
+                        ad.log.warning('Cycle %s: %s', attempt+1, value)
+
+                try:
+                    fail_rate = (len(result) - result.count(True))/len(result)
+                    ad.log.info(
+                        'Fail rate of IMS registration at slot %s: %s',
+                        voice_slot,
+                        fail_rate)
+                except Exception as e:
+                    ad.log.error(
+                        'Fail rate of IMS registration at slot %s: ERROR (%s)',
+                        voice_slot,
+                        e)
+
+                ad.log.info(
+                    'Number of trials with valid parsed logs: %s', len(ims_reg))
+                ad.log.info(
+                    'Average IMS registration time at slot %s: %s',
+                    voice_slot, avg_ims_reg_duration)
+
+            if exit_due_to_high_fail_rate:
+                break
+
+        return test_result(result, cycle)
+
+    @test_tracker_info(uuid="ce69fac3-931b-4177-82ea-dbae50b2b310")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ims_handover_iwlan_to_4g_wfc_cellular_preferred(self):
+        """Turn off airplane mode to make IMS registration hand over from iwlan to LTE
+            in WFC cellular-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC, set WFC mode to cellular-preferred mode, turn on
+                airplane mode and then connect Wi-Fi to let IMS register over
+                iwlan.
+            2. Turn off airplane mode. The IMS registration should hand over
+                from iwlan to LTE.
+            3. Parse logcat to calculate the IMS handover time.
+        """
+        return self.ims_handover_iwlan_to_4g_wfc_cellular_preferred(False)
+
+    @test_tracker_info(uuid="0ac7d43e-34e6-4ea3-92f4-e413e90a8bc1")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_ims_handover_iwlan_to_4g_with_voice_call_wfc_cellular_preferred(self):
+        """Turn off airplane mode to make IMS registration hand over from iwlan to LTE
+            in WFC cellular-preferred mode. Measure IMS handover time.
+
+        Test steps:
+            1. Enable WFC, set WFC mode to cellular-preferred mode, turn on
+                airplane mode and then connect Wi-Fi to let IMS register over
+                iwlan.
+            2. Make a WFC call and keep the call active.
+            3. Turn off airplane mode. The IMS registration should hand over
+                from iwlan to LTE.
+            4. Parse logcat to calculate the IMS handover time.
+        """
+        return self.ims_handover_iwlan_to_4g_wfc_cellular_preferred(True)
\ No newline at end of file
diff --git a/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py b/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
index 0b8ce5d..9598616 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
@@ -36,7 +36,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import get_network_rat
 from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
 from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
diff --git a/acts_tests/tests/google/tel/live/TelLiveSmsTest.py b/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
index 0cae89c..83180a9 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
@@ -34,8 +34,8 @@
 from acts_contrib.test_utils.tel.tel_test_utils import remove_mobile_data_usage_limit
 from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import set_mobile_data_usage_limit
-from acts_contrib.test_utils.tel.tel_test_utils import sms_in_collision_send_receive_verify
-from acts_contrib.test_utils.tel.tel_test_utils import sms_rx_power_off_multiple_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_in_collision_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_rx_power_off_multiple_send_receive_verify
 from acts_contrib.test_utils.tel.tel_message_utils import message_test
 from acts.utils import rand_ascii_str
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py b/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
index 685ce5e..20aa583 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
@@ -22,8 +22,8 @@
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_loggers
 from acts_contrib.test_utils.tel.tel_test_utils import call_setup_teardown
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phone_subscription
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
@@ -31,9 +31,8 @@
 from acts_contrib.test_utils.tel.tel_test_utils import last_call_drop_reason
 from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
 from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import start_sdm_loggers
 from acts_contrib.test_utils.tel.tel_test_utils import verify_incall_state
 from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
@@ -43,16 +42,13 @@
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
 from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_iwlan
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_volte
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
 from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
 from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup
-from acts_contrib.test_utils.tel.tel_video_utils import \
-    is_phone_in_call_video_bidirectional
-from acts.logger import epoch_to_log_line_timestamp
+from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py b/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
index 46bef20..017cebf 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
@@ -39,7 +39,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import is_droid_in_network_generation
 from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_network_generation
 from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
 from acts_contrib.test_utils.tel.tel_test_utils import wait_for_state
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py b/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py
index b593781..23e0a68 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressSmsTest.py
@@ -27,7 +27,7 @@
 from acts_contrib.test_utils.tel.tel_test_utils import ensure_phones_idle
 from acts_contrib.test_utils.tel.tel_test_utils import install_message_apk
 from acts_contrib.test_utils.tel.tel_test_utils import multithread_func
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_voice_utils \
     import phone_setup_volte_for_subscription
 from acts_contrib.test_utils.tel.tel_voice_utils \
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressTest.py b/acts_tests/tests/google/tel/live/TelLiveStressTest.py
index 03dc18a..a3859c4 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressTest.py
@@ -30,8 +30,6 @@
 from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
 from acts_contrib.test_utils.tel.loggers.telephony_stress_metric_logger import TelephonyStressMetricLogger
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
 from acts_contrib.test_utils.tel.tel_defines import GEN_3G
 from acts_contrib.test_utils.tel.tel_defines import GEN_4G
 from acts_contrib.test_utils.tel.tel_defines import GOOGLE_CBRS_CARRIER_ID
@@ -48,8 +46,7 @@
 from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_MESSAGE_SUB_ID
 from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_VOICE_SUB_ID
-from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_CBRS_DATA_SWITCH
-from acts_contrib.test_utils.tel.tel_defines import CARRIER_SING
+from acts_contrib.test_utils.tel.tel_logging_utils import start_sdm_loggers
 from acts_contrib.test_utils.tel.tel_lookup_tables import is_rat_svd_capable
 from acts_contrib.test_utils.tel.tel_test_utils import STORY_LINE
 from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_test
@@ -67,9 +64,8 @@
 from acts_contrib.test_utils.tel.tel_test_utils import last_call_drop_reason
 from acts_contrib.test_utils.tel.tel_test_utils import run_multithread_func
 from acts_contrib.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_message_utils import sms_send_receive_verify
 from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts_contrib.test_utils.tel.tel_test_utils import start_sdm_loggers
 from acts_contrib.test_utils.tel.tel_test_utils import start_adb_tcpdump
 from acts_contrib.test_utils.tel.tel_test_utils import synchronize_device_time
 from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
diff --git a/acts_tests/tests/google/wifi/OWNERS b/acts_tests/tests/google/wifi/OWNERS
index 00010b6..10e4214 100644
--- a/acts_tests/tests/google/wifi/OWNERS
+++ b/acts_tests/tests/google/wifi/OWNERS
@@ -1,7 +1,5 @@
 bkleung@google.com
-dysu@google.com
-etancohen@google.com
 gmoturu@google.com
-rpius@google.com
-satk@google.com
 hsiuchangchen@google.com
+
+include platform/packages/modules/Wifi:/WIFI_OWNERS
diff --git a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
index 916315b..5180f50 100755
--- a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
+++ b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
@@ -98,17 +98,15 @@
                 "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 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)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
@@ -137,7 +135,7 @@
                 wifi_ap[WifiEnums.SSID_KEY] == self.wifi_hotspot[WifiEnums.SSID_KEY],
                 "Hotspot SSID doesn't match with expected SSID")
             return
-        elif self.dut.build_info["build_id"].startswith("Q"):
+        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(
@@ -242,7 +240,22 @@
             networks: List of network dicts.
         """
         network_info = self.dut.droid.wifiGetConfiguredNetworks()
-        if len(network_info) != len(networks):
+        """
+            b/189285598, the network id of a network might be changed after \
+            reboot because the network sequence is not stable on \
+            backup/restore. This test should find the correct network against
+            the desired SSID before connecting to the network after reboot.
+            Start from Android S.
+        """
+        network_info_ssid = list()
+        for i in network_info:
+            network_info_ssid.append(i['SSID'])
+        network_info_ssid_list = set(network_info_ssid)
+        networks_ssid= list()
+        for i in networks:
+            networks_ssid.append(i['SSID'])
+        networks_ssid_list = set(networks_ssid)
+        if len(network_info_ssid_list) != len(networks_ssid_list):
             msg = (
                 "Number of configured networks before and after Auto-update "
                 "don't match. \nBefore reboot = %s \n After reboot = %s" %
@@ -257,6 +270,7 @@
                 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"]
 
diff --git a/acts_tests/tests/google/wifi/WifiBridgedApTest.py b/acts_tests/tests/google/wifi/WifiBridgedApTest.py
new file mode 100644
index 0000000..87b980c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiBridgedApTest.py
@@ -0,0 +1,835 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2021 - 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
+from acts import asserts
+from acts import signals
+from acts.test_decorators import test_tracker_info
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.controllers.ap_lib.hostapd_constants import BAND_2G
+from acts.controllers.ap_lib.hostapd_constants import BAND_5G
+
+
+WifiEnums = wutils.WifiEnums
+BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS = 5
+BRIDGED_AP_SHUTDOWN_INTERVAL_5_MINUTES = 300
+BRIDGED_AP_SHUTDOWN_INTERVAL_5_SECONDS = 5
+SOFT_AP_SHUTDOWN_INTERVAL_10_MINUTES = 600
+INTERVAL_9_MINUTES = 540
+INTERVAL_2_MINUTES = 120
+INTERVAL_1_MINUTES = 60
+
+
+class WifiBridgedApTest(WifiBaseTest):
+    """WiFi BridgedAp test class.
+
+    Test Bed Requirement:
+        * Android device x 1 with BridgedAp supported.
+        * Android devices x2 as clients, at least Android 10.
+        * OpenWrt AP x 1.
+    """
+
+    def setup_class(self):
+        super().setup_class()
+
+        if len(self.android_devices) == 3:
+            self.dut = self.android_devices[0]
+            self.client1 = self.android_devices[1]
+            self.client2 = self.android_devices[2]
+        else:
+            raise signals.TestAbortClass("WifiBridgedApTest requires 3 DUTs")
+
+        if not self.dut.droid.wifiIsBridgedApConcurrencySupported():
+            raise signals.TestAbortClass("Legacy phone is not supported")
+
+        for ad in self.android_devices:
+            wutils.wifi_test_device_init(ad)
+
+        req_params = ["dbs_supported_models"]
+        opt_param = ["cnss_diag_file", "pixel_models"]
+
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+    def setup_test(self):
+        super().setup_test()
+        for ad in self.android_devices:
+            wutils.reset_wifi(ad)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.client1, True)
+        wutils.wifi_toggle_state(self.client2, True)
+
+    def teardown_test(self):
+        super().teardown_test()
+        if self.dut.droid.wifiIsApEnabled():
+            wutils.stop_wifi_tethering(self.dut)
+        for ad in self.android_devices:
+            wutils.reset_wifi(ad)
+            wutils.set_wifi_country_code(
+                ad, wutils.WifiEnums.CountryCode.US)
+
+    def teardown_class(self):
+        super().teardown_class()
+        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"]
+
+    def set_country_code_and_verify(self, ad, country_code):
+        wutils.set_wifi_country_code(ad, country_code)
+        # Wi-Fi ON and OFF to make sure country code take effect.
+        wutils.wifi_toggle_state(ad, True)
+        wutils.wifi_toggle_state(ad, False)
+
+        country = ad.droid.wifiGetCountryCode()
+        asserts.assert_true(country == country_code,
+                            "country code {} is not set".format(country_code))
+        ad.log.info("code code set to : {}".format(country))
+
+    def verify_clients_support_wpa3_sae(self, *args):
+        """Check if clients support WPA3 SAE.
+
+        Args:
+            args: arbitrary number of clients. e.g., self.dut1, self.dut2, ...
+        """
+        duts = args
+        for dut in duts:
+            asserts.skip_if(not dut.droid.wifiIsWpa3SaeSupported(),
+                            "All DUTs support WPA3 SAE is required")
+
+    def verify_band_of_bridged_ap_instance(self, ad, infos, bands):
+        """Check bands enabled as expected.
+           This function won't be called directly.
+
+        Args:
+            infos: SoftAp info list.
+            bands: A list of bands.
+                   e,g,. [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                          WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G]
+        """
+        asserts.assert_true(len(infos) == len(bands),
+                            "length of infos and bands not matched")
+        if len(bands) == 1 and (bands[0] == WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G):
+            asserts.assert_true(infos[0][wifi_constants.
+                                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+                                in WifiEnums.softap_band_frequencies[bands[0]],
+                                "This should be a %s instance", bands[0])
+        if len(bands) == 2 and (bands[0] == WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G):
+            asserts.assert_true((infos[0][wifi_constants.
+                                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+                                in WifiEnums.ALL_2G_FREQUENCIES and
+                                infos[1][wifi_constants.
+                                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+                                in WifiEnums.ALL_5G_FREQUENCIES) or
+                                (infos[0][wifi_constants.
+                                 SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+                                 in WifiEnums.ALL_5G_FREQUENCIES and
+                                 infos[1][wifi_constants.
+                                 SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+                                 in WifiEnums.ALL_2G_FREQUENCIES),
+                                "There should only be 2G and 5G instances")
+
+    def verify_softap_freq_equals_to_ap_freq(self, ad, infos):
+        """Verify if instance frequency equal to AP frequency.
+           This function won't be called directly.
+        Args:
+            infos: SoftAp info list.
+        """
+        wlan0_freq = wutils.get_wlan0_link(ad)["freq"]
+        softap_freqs = []
+        for i in range(len(infos)):
+            softap_freqs.append(infos[i][WifiEnums.frequency_key])
+        ad.log.info("softap_freqs : {}".format(softap_freqs))
+        asserts.assert_true(int(wlan0_freq) in softap_freqs,
+                            "AP freq != SoftAp freq")
+        ad.log.info("softap freq == AP freq")
+
+    def verify_number_band_freq_of_bridged_ap(self, ad, bands,
+                                              freq_equal=False):
+        """Verify every aspect of info list from BridgedAp.
+
+        Args:
+            bands: A list of bands,
+                   e,g,. [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                          WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G]
+            freq_equal: True if need to check SoftAp freq equals to STA freq.
+        """
+        callbackId = ad.droid.registerSoftApCallback()
+        infos = wutils.get_current_softap_infos(ad, callbackId, True)
+        self.verify_band_of_bridged_ap_instance(ad, infos, bands)
+        if freq_equal:
+            self.verify_softap_freq_equals_to_ap_freq(ad, infos)
+        ad.droid.unregisterSoftApCallback(callbackId)
+
+    def verify_expected_number_of_softap_clients(self, ad, number):
+        """Verify the number of softap clients.
+
+        Args:
+            number: expect number of client connect to SoftAp.
+        """
+        callbackId = self.dut.droid.registerSoftApCallback()
+        wutils.wait_for_expected_number_of_softap_clients(self.dut,
+                                                          callbackId, number)
+        ad.log.info("{} clients connect to soft ap".format(number))
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+    def wait_interval(self, interval):
+        """print different messages with different intervals.
+
+        Args:
+            interval: different interval for different situation.
+        """
+        if interval == BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS:
+            logging.info("Wait {} seconds for BridgedAp launch"
+                         .format(interval))
+            time.sleep(interval)
+        elif interval == BRIDGED_AP_SHUTDOWN_INTERVAL_5_MINUTES:
+            logging.info("Wait {} minutes for BridgedAp shutdown"
+                         .format(interval/60))
+            time.sleep(interval)
+        elif interval == SOFT_AP_SHUTDOWN_INTERVAL_10_MINUTES:
+            logging.info("Wait {} minutes for SoftAp shutdown"
+                         .format(interval/60))
+            time.sleep(interval)
+        elif interval == INTERVAL_9_MINUTES:
+            logging.info("Wait {} minutes".format(interval/60))
+            time.sleep(interval)
+        elif interval == INTERVAL_2_MINUTES:
+            logging.info("Wait {} minutes".format(interval/60))
+            time.sleep(interval)
+        elif interval == INTERVAL_1_MINUTES:
+            logging.info("Wait {} minutes".format(interval/60))
+            time.sleep(interval)
+
+    def two_clients_connect_to_wifi_network(self, dut1, dut2, config):
+        """Connect two clients to different BridgedAp instances.
+           This function will be called only when BridgedAp ON.
+
+        Args:
+            config: Wi-Fi config, e.g., {"SSID": "xxx", "password": "xxx"}
+        Steps:
+            Backup config.
+            Register SoftAp Callback.
+            Get SoftAp Infos.
+            Get BSSIDs from Infos.
+            Connect two clients to different BridgedAp instances.
+            Restore config.
+        """
+        # Make sure 2 instances enabled, and get BSSIDs from BridgedAp Infos.
+        callbackId = self.dut.droid.registerSoftApCallback()
+        infos = wutils.get_current_softap_infos(self.dut, callbackId, True)
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+
+        if len(infos) == 0:
+            raise signals.TestFailure("No BridgedAp instance")
+        elif len(infos) == 1:
+            raise signals.TestFailure(
+                "Only one BridgedAp instance, should be two")
+        else:
+            bssid_5g = infos[0][wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+            bssid_2g = infos[1][wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+
+        # Two configs for BridgedAp 2G and 5G instances.
+        config_5g = config.copy()
+        config_2g = config.copy()
+        config_5g[WifiEnums.BSSID_KEY] = bssid_5g
+        config_2g[WifiEnums.BSSID_KEY] = bssid_2g
+
+        # Connect two clients to BridgedAp.
+        wutils.connect_to_wifi_network(dut1, config_5g,
+                                       check_connectivity=False)
+        wutils.connect_to_wifi_network(dut2, config_2g,
+                                       check_connectivity=False)
+
+        # Verify if Clients connect to the expected BridgedAp instances.
+        client1_bssid = wutils.get_wlan0_link(
+            self.client1)[wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+        client2_bssid = wutils.get_wlan0_link(
+            self.client2)[wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+        asserts.assert_true(client1_bssid == bssid_5g,
+                            "Client1 does not connect to the 5G instance")
+        asserts.assert_true(client2_bssid == bssid_2g,
+                            "Client2 does not connect to the 2G instance")
+
+    # Tests
+
+    @test_tracker_info(uuid="6f776b4a-b080-4b52-a330-52aa641b18f2")
+    def test_two_clients_ping_on_bridged_ap_band_2_and_5_with_wpa3_in_country_us(self):
+        """Test clients on different instances can ping each other.
+
+        Steps:
+            Backup config.
+            Make sure clients support WPA3 SAE.
+            Make sure DUT is able to enable BridgedAp.
+            Enable BridgedAp with bridged configuration.
+            RegisterSoftApCallback.
+            Check the bridged AP enabled succeed.
+            Force client#1 connect to 5G.
+            Force client#2 connect to 2.4G.
+            Trigger client#1 and client#2 each other.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure clients support WPA3 SAE.
+        self.verify_clients_support_wpa3_sae(self.client1, self.client2)
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        self.two_clients_connect_to_wifi_network(self.client1, self.client2,
+                                                 config)
+        # Trigger client#1 and client#2 ping each other.
+        wutils.validate_ping_between_two_clients(self.client1, self.client2)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="0325ee58-ed8e-489e-9dee-55740406f896")
+    def test_bridged_ap_5g_2g_and_sta_5g_dfs(self):
+        """Test if auto fallback to one single 2G AP mode when
+         BridgedAP enabled and STA connect to a 5G DFS channel.
+
+        Steps:
+            Backup config.
+            DUT enable BridgedAp.
+            DUT connect to a 5G DFS channel Wi-Fi network.
+            Verify 5G instance is shutdown.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], False)
+
+        # STA connect to a 5G DFS Channel.
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=6,
+                                                channel_5g=132)
+            wutils.connect_to_wifi_network(self.dut,
+                                           self.wpa_networks[0][BAND_5G])
+
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="9a5d4ca9-67fc-412c-8114-01c43c34a76d")
+    def test_bridged_ap_5g_2g_and_sta_5g_non_dfs(self):
+        """Test 5G scc when BridgedAp enabled and 5G STA.
+
+        Steps:
+            Backup config.
+            DUT enable BridgedAp.
+            DUT connect to a 5G non-DFS channel Wi-Fi network.
+            Verify STA frequency equals to 5G BridgedAp frequency.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        # STA connect to a 5G Non-DFS Channel.
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=6,
+                                                channel_5g=36)
+            wutils.connect_to_wifi_network(self.dut,
+                                           self.wpa_networks[0][BAND_5G])
+
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], True)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="e26e8a72-3e21-47b3-8fd8-4c4db5fb2573")
+    def test_bridged_ap_5g_2g_and_sta_2g(self):
+        """ Test 2G SCC when BridgedAp enable and 2G STA.
+
+        Steps:
+            Backup config.
+            DUT enable BridgedAp.
+            STA connect to a 2G Wi-Fi network.
+            Verify STA frequency equals to 2G BridgedAp frequency.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        # STA connect to a 2G Channel.
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=6,
+                                                channel_5g=36)
+            wutils.connect_to_wifi_network(self.dut,
+                                           self.wpa_networks[0][BAND_2G])
+
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], True)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="b25f710a-2e53-456e-8280-dcdd37badd6c")
+    def test_sta_5g_dfs_and_bridged_ap_5g_2g(self):
+        """Test auto fallback to Single AP mode
+           when STA connect to a DFS channel.
+
+        Steps:
+            Backup config.
+            DUT connect to a 5G DFS channel Wi-Fi network.
+            DUT enable BridgedAp.
+            Verify 5G instance is shutdown and only 2G instance enabled.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # STA connect to a 5G DFS Channel.
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=6,
+                                                channel_5g=132)
+            wutils.connect_to_wifi_network(self.dut,
+                                           self.wpa_networks[0][BAND_5G])
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        # # Verify only 2G instance enabled.
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="6acaed3f-616f-4a03-a329-702e6cc537bd")
+    def test_sta_5g_non_dfs_and_bridged_ap_5g_2g(self):
+        """Test 5G scc when 5G STA and BridgedAp enabled.
+
+        Steps:
+            Backup config.
+            DUT connect to a 5G non-DFS channel Wi-Fi network.
+            DUT enable BridgedAp.
+            Verify STA frequency equals to 5G BridgedAp frequency.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # STA connect to a 5G non-DFS Channel.
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=6,
+                                                channel_5g=36)
+            wutils.connect_to_wifi_network(self.dut,
+                                           self.wpa_networks[0][BAND_5G])
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        # Verify STA frequency equals to 5G BridgedAp frequency.
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], True)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="e24e7f2d-f1d5-4dc7-aa6a-487677864d1d")
+    def test_sta_2g_and_bridged_ap_5g_2g(self):
+        """ Test 2G SCC when 2G STA and BridgedAp enable.
+
+        Steps:
+            Backup config.
+            DUT connect to a 2G Wi-Fi network.
+            DUT enable BridgedAp.
+            Verify STA frequency equals to 2G BridgedAp frequency.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # STA connect to a 2G Channel.
+        wutils.wifi_toggle_state(self.dut, True)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=6,
+                                                channel_5g=36)
+            wutils.connect_to_wifi_network(self.dut,
+                                           self.wpa_networks[0][BAND_2G])
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], True)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="927d564d-2ac4-4e6f-bb36-270efd519e0b")
+    def test_bridged_ap_5g_2g_shutdown_5g_2g_no_client(self):
+        """Test the BridgeAp shutdown mechanism with no client connect to it.
+
+        Steps:
+            Backup config.
+            DUT turns ON BridgedAp.
+            Verify no client connect to the BridgedAp.
+            Wait for 5 minutes.
+            Verify that 5G BridgedAp instance is shutdown.
+            Maks sure there is still no client connect to the BridgedAp.
+            Wait for 5 minutes.
+            Verify that 2G BridgedAp instance is shutdown.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        # Wait 5 seconds for BridgedAp launch.
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        # No client connection, wait 5 minutes, verify 5G is shutdown.
+        self.verify_expected_number_of_softap_clients(self.dut, 0)
+        self.wait_interval(BRIDGED_AP_SHUTDOWN_INTERVAL_5_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+        # No client connection, wait 5 minutes, verify 2G is shutdown.
+        self.verify_expected_number_of_softap_clients(self.dut, 0)
+        self.wait_interval(BRIDGED_AP_SHUTDOWN_INTERVAL_5_MINUTES)
+
+        self.verify_number_band_freq_of_bridged_ap(self.dut, [], False)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="4c5b210c-412f-40e4-84b6-2a12dbffa017")
+    def test_bridged_ap_5g_2g_shutdown_5g_auto_shutdown_2g(self):
+        """Test the BridgeAp shutdown mechanism with no client connect to it.
+
+        Steps:
+            Backup config.
+            DUT turns ON BridgedAp.
+            Verify no client connect to the BridgedAp.
+            Wait for 5 minutes.
+            Verify that 5G BridgedAp instance is shutdown.
+            A client connect to the 2G BridgedAp.
+            The client disconnect from 2G BridgedAp.
+            Wait for 10 minutes.
+            Verify 2G BridgedAp is shutdown, no BridgedAp instance enabled.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], False)
+
+        # Verify no connection to BridgedAp, wait for 5 mins, 5G shutdown.
+        self.verify_expected_number_of_softap_clients(self.dut, 0)
+        self.wait_interval(BRIDGED_AP_SHUTDOWN_INTERVAL_5_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        # A client connect to 2G BridgedAp instance.
+        callbackId = self.dut.droid.registerSoftApCallback()
+        infos = wutils.get_current_softap_infos(self.dut, callbackId, True)
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+        bssid_2g = infos[0][wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+        config_2g = config.copy()
+        config_2g[WifiEnums.BSSID_KEY] = bssid_2g
+        wutils.connect_to_wifi_network(self.client1, config_2g,
+                                       check_connectivity=False)
+        self.wait_interval(BRIDGED_AP_SHUTDOWN_INTERVAL_5_SECONDS)
+
+        # Client disconnect From 2G BridgedAp instance.
+        is_disconnect = self.client1.droid.wifiDisconnect()
+        self.client1.log.info("Disconnected from SoftAp")
+        if not is_disconnect:
+            raise signals.TestFailure("Wi-Fi is not disconnected as expect")
+        wutils.reset_wifi(self.client1)
+
+        # Verify 2G instance is still enabled.
+        self.wait_interval(INTERVAL_9_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        # Verify all BridgedAp instances are shutdown after 10 minutes.
+        self.wait_interval(INTERVAL_1_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(self.dut, [], False)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="2b1b4579-d610-4983-83f4-5fc34b3cdd6b")
+    def test_bridged_ap_5g_2g_shutdown_5g_auto_shutdown_2g_after_10_mins(self):
+        """Test the BridgeAp shutdown mechanism with no client connect to it.
+
+        Steps:
+            Backup config.
+            DUT turns ON BridgedAp.
+            Verify no client connect to the BridgedAp.
+            Wait for 5 minutes.
+            Verify that 5G BridgedAp instance is shutdown.
+            A client connect to the 2G BridgedAp at 7th minutes.
+            The client disconnect from 2G BridgedAp at 9th minutes.
+            Wait for 10 minutes.
+            Verify 2G BridgedAp is shutdown, no BridgedAp instance enabled.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Make sure DUT is able to enable BridgedAp.
+        is_supported = wutils.check_available_channels_in_bands_2_5(
+            self.dut, wutils.WifiEnums.CountryCode.US)
+        asserts.skip_if(not is_supported, "BridgedAp is not supported in {}"
+                        .format(wutils.WifiEnums.CountryCode.US))
+
+        # Enable BridgedAp
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G],
+            bridged_opportunistic_shutdown_enabled=True,
+            shutdown_timeout_enable=True)
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], False)
+        self.verify_expected_number_of_softap_clients(self.dut, 0)
+        self.wait_interval(BRIDGED_AP_SHUTDOWN_INTERVAL_5_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        # Client connects to 2G instance at 7th mins.
+        self.wait_interval(INTERVAL_2_MINUTES)
+        callbackId = self.dut.droid.registerSoftApCallback()
+        infos = wutils.get_current_softap_infos(self.dut, callbackId, True)
+        self.dut.droid.unregisterSoftApCallback(callbackId)
+        bssid_2g = infos[0][wifi_constants.SOFTAP_INFO_BSSID_CALLBACK_KEY]
+        config_2g = config.copy()
+        config_2g[WifiEnums.BSSID_KEY] = bssid_2g
+        wutils.connect_to_wifi_network(self.client1, config_2g,
+                                       check_connectivity=False)
+        self.wait_interval(BRIDGED_AP_SHUTDOWN_INTERVAL_5_SECONDS)
+
+        # Client disconnect From 2G BridgedAp instance at 9th mins.
+        self.wait_interval(INTERVAL_2_MINUTES)
+        is_disconnect = self.client1.droid.wifiDisconnect()
+        self.client1.log.info("Disconnected from SoftAp")
+        if not is_disconnect:
+            raise signals.TestFailure("Wi-Fi is not disconnected as expect")
+        wutils.reset_wifi(self.client1)
+
+        # Make sure 2G instance is still enabled.
+        self.wait_interval(INTERVAL_9_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        self.wait_interval(INTERVAL_1_MINUTES)
+        self.verify_number_band_freq_of_bridged_ap(self.dut, [], False)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+
+    @test_tracker_info(uuid="1785a487-dd95-4c17-852d-3c9b7b7dd4c3")
+    def test_bridged_ap_5g_2g_fallback_2g_country_jp(self):
+        """Test BridgedAp fallback to Single AP mode with JP country code.
+
+        Steps:
+            Backup config.
+            Set DUT country code to "JP".
+            DUT turns ON BridgedAp.
+            Verify only 2G BridgedAp instance is enabled.
+            Restore config.
+        """
+        # Backup config
+        original_softap_config = self.dut.droid.wifiGetApConfiguration()
+
+        # Set country code to JP and enable BridgedAp
+        self.set_country_code_and_verify(self.dut, WifiEnums.CountryCode.JAPAN)
+        config = wutils.create_softap_config()
+        config[WifiEnums.SECURITY] = WifiEnums.SoftApSecurityType.WPA3_SAE
+        wutils.save_wifi_soft_ap_config(
+            self.dut, config,
+            bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
+                   WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        self.wait_interval(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
+
+        # Verify only 2G BridgedAp instance enabled.
+        self.verify_number_band_freq_of_bridged_ap(
+            self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G], False)
+
+        # Restore config
+        wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
diff --git a/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py b/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
index 5b6ab41..e957f59 100644
--- a/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
@@ -59,6 +59,7 @@
             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()
@@ -78,6 +79,7 @@
             WifiEnums.NONE_DFS_5G_FREQUENCIES)
 
     def teardown_test(self):
+        super().teardown_test()
         for ad in self.android_devices:
             ad.droid.wakeLockRelease()
             ad.droid.goToSleepNow()
@@ -92,9 +94,7 @@
             wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
         except signals.TestFailure:
             pass
-        for ad in self.android_devices:
-            ad.take_bug_report(test_name, begin_time)
-            ad.cat_adb_log(test_name, begin_time)
+        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.
diff --git a/acts_tests/tests/google/wifi/WifiChaosTest.py b/acts_tests/tests/google/wifi/WifiChaosTest.py
index f73ca6c..1056dd6 100755
--- a/acts_tests/tests/google/wifi/WifiChaosTest.py
+++ b/acts_tests/tests/google/wifi/WifiChaosTest.py
@@ -112,6 +112,7 @@
         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)
@@ -155,6 +156,7 @@
         return True
 
     def setup_test(self):
+        super().setup_test()
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
 
@@ -165,10 +167,10 @@
         # Sleep to ensure all failed packets are captured.
         time.sleep(5)
         wutils.stop_pcap(self.pcap, self.pcap_procs, False)
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.cat_adb_log(test_name, begin_time)
+        super().on_fail(test_name, begin_time)
 
     def teardown_test(self):
+        super().teardown_test()
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
 
diff --git a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
index 98061fd..8165c72 100644
--- a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
+++ b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
@@ -83,21 +83,19 @@
         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 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)
-
     def teardown_class(self):
         wutils.stop_wifi_tethering(self.dut_softap)
         self.reset_mac_address_to_factory_mac()
diff --git a/acts_tests/tests/google/wifi/WifiCrashStressTest.py b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
index 5f61c63..90e546f 100644
--- a/acts_tests/tests/google/wifi/WifiCrashStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
@@ -33,6 +33,10 @@
     * 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()
 
@@ -40,15 +44,15 @@
         self.dut_client = self.android_devices[1]
         wutils.wifi_test_device_init(self.dut)
         wutils.wifi_test_device_init(self.dut_client)
-        if not self.dut.is_apk_installed("com.google.mdstest"):
-            raise signals.TestAbortClass("mdstest is not installed")
-        req_params = ["dbs_supported_models", "stress_count"]
+        req_params = ["sta_sta_supported_models", "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()
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True)
 
         asserts.assert_true(
             len(self.reference_networks) > 0,
@@ -57,8 +61,11 @@
         self.ap_iface = 'wlan0'
         if self.dut.model in self.dbs_supported_models:
             self.ap_iface = 'wlan1'
+        if self.dut.model in self.sta_sta_supported_models:
+            self.ap_iface = 'wlan2'
 
     def setup_test(self):
+        super().setup_test()
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
         wutils.wifi_toggle_state(self.dut, True)
@@ -67,6 +74,7 @@
         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()
@@ -76,12 +84,6 @@
         self.dut_client.droid.goToSleepNow()
         wutils.reset_wifi(self.dut_client)
 
-    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)
-        self.dut_client.take_bug_report(test_name, begin_time)
-        self.dut_client.cat_adb_log(test_name, begin_time)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
@@ -89,15 +91,8 @@
     """Helper Functions"""
     def trigger_wifi_firmware_crash(self, ad, timeout=15):
         pre_timestamp = ad.adb.getprop("vendor.debug.ssrdump.timestamp")
-        ad.adb.shell(
-            "setprop persist.vendor.sys.modem.diag.mdlog false", ignore_status=True)
-        # Legacy pixels use persist.sys.modem.diag.mdlog.
-        ad.adb.shell(
-            "setprop persist.sys.modem.diag.mdlog false", ignore_status=True)
         disable_qxdm_logger(ad)
-        cmd = ('am instrument -w -e request "4b 25 03 b0 00" '
-               '"com.google.mdstest/com.google.mdstest.instrument.'
-               'ModemCommandInstrumentation"')
+        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
diff --git a/acts_tests/tests/google/wifi/WifiCrashTest.py b/acts_tests/tests/google/wifi/WifiCrashTest.py
index 3982092..a8b0db6 100644
--- a/acts_tests/tests/google/wifi/WifiCrashTest.py
+++ b/acts_tests/tests/google/wifi/WifiCrashTest.py
@@ -32,8 +32,12 @@
 # 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"
+WIFI_VENDOR_EXT_HAL_DAEMON = "vendor.google.wifi_ext@1.0-service-vendor"
+WIFI_VENDOR_EXT_HAL_DAEMON_PREFIX = "vendor.google.wifi_ext@"
+WIFI_VENDOR_EXT_HAL_DAEMON_KILL_SHELL_COMMAND = "killall vendor.google.wifi_ext@1.0-service-vendor"
+WIFI_VENDOR_HAL_DAEMON = "android.hardware.wifi@1.0-service"
+WIFI_VENDOR_HAL_DAEMON_KILL_SHELL_COMMAND = "killall android.hardware.wifi@1.0-service"
 
 class WifiCrashTest(WifiBaseTest):
     """Crash Tests for wifi stack.
@@ -42,6 +46,9 @@
     * 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()
@@ -55,6 +62,8 @@
 
         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,
@@ -62,19 +71,17 @@
         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 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)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
@@ -143,7 +150,14 @@
         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)
+        daemon_search_cmd = "ps -ef | grep %s" % WIFI_VENDOR_EXT_HAL_DAEMON_PREFIX
+        result = self.dut.adb.shell(daemon_search_cmd) or ""
+        if WIFI_VENDOR_EXT_HAL_DAEMON in result:
+            self.log.info("Crashing wifi ext HAL")
+            self.dut.adb.shell(WIFI_VENDOR_EXT_HAL_DAEMON_KILL_SHELL_COMMAND)
+        else:
+            self.log.info("Crashing wifi HAL")
+            self.dut.adb.shell(WIFI_VENDOR_HAL_DAEMON_KILL_SHELL_COMMAND)
         wutils.wait_for_disconnect(self.dut)
         time.sleep(RECOVERY_TIMEOUT)
         wifi_info = self.dut.droid.wifiGetConnectionInfo()
diff --git a/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py b/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
index d72e066..a166b9d 100644
--- a/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
+++ b/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
@@ -60,19 +60,16 @@
         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 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)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["open_network"]
diff --git a/acts_tests/tests/google/wifi/WifiDppTest.py b/acts_tests/tests/google/wifi/WifiDppTest.py
index 6252cbf..07e2488 100644
--- a/acts_tests/tests/google/wifi/WifiDppTest.py
+++ b/acts_tests/tests/google/wifi/WifiDppTest.py
@@ -46,22 +46,31 @@
   DPP_TEST_EVENT_CONFIGURATOR_SUCCESS = "onConfiguratorSuccess"
   DPP_TEST_EVENT_PROGRESS = "onProgress"
   DPP_TEST_EVENT_FAILURE = "onFailure"
+  DPP_TEST_EVENT_URI_GENERATED = "onBootstrapUriGenerated"
   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_MESSAGE_GENERATED_URI = "generatedUri"
 
   DPP_TEST_NETWORK_ROLE_STA = "sta"
   DPP_TEST_NETWORK_ROLE_AP = "ap"
 
   DPP_TEST_PARAM_SSID = "SSID"
-  DPP_TEST_PARAM_PASSWORD = "Password"
+  DPP_TEST_PARAM_PASSWORD = "password"
 
   WPA_SUPPLICANT_SECURITY_SAE = "sae"
   WPA_SUPPLICANT_SECURITY_PSK = "psk"
 
+  DPP_TEST_CRYPTOGRAPHY_CURVE_PRIME256V1 = "prime256v1"
+  DPP_TEST_CRYPTOGRAPHY_CURVE_SECP384R1 = "secp384r1"
+  DPP_TEST_CRYPTOGRAPHY_CURVE_SECP521R1 = "secp521r1"
+  DPP_TEST_CRYPTOGRAPHY_CURVE_BRAINPOOLP256R1 = "brainpoolP256r1"
+  DPP_TEST_CRYPTOGRAPHY_CURVE_BRAINPOOLP384R1 = "brainpoolp384r1"
+  DPP_TEST_CRYPTOGRAPHY_CURVE_BRAINPOOLP512R1 = "brainpoolP512r1"
+
   DPP_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0
   DPP_EVENT_PROGRESS_RESPONSE_PENDING = 1
   DPP_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2
@@ -77,7 +86,7 @@
         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]
@@ -93,6 +102,11 @@
     opt_param = ["wifi_psk_network", "wifi_sae_network"]
     self.unpack_userparams(
       req_param_names=req_params, opt_param_names=opt_param)
+    if "OpenWrtAP" in self.user_params:
+      self.configure_openwrt_ap_and_start(wpa_network=True,
+                                          sae_network=True)
+      self.wifi_psk_network = self.wpa_networks[0]["5g"].copy()
+      self.wifi_sae_network = self.sae_networks[0]["2g"].copy()
 
     self.dut.log.info(
       "Parsed configs: %s %s" % (self.wifi_psk_network, self.wifi_sae_network))
@@ -132,10 +146,6 @@
   def teardown_class(self):
     wutils.reset_wifi(self.dut)
 
-  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)
-
   def create_and_save_wifi_network_config(self, security, random_network=False,
                                           r2_auth_error=False):
     """ Create a config with random SSID and password.
@@ -149,7 +159,8 @@
                A tuple with the config and networkId for the newly created and
                saved network.
     """
-    if security == self.DPP_TEST_SECURITY_PSK:
+    if security == self.DPP_TEST_SECURITY_PSK or \
+              security == self.DPP_TEST_SECURITY_PSK_PASSPHRASE:
       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)
@@ -248,7 +259,7 @@
     self.del_uri(device, "'*'")
 
     self.log.info("Generating a URI for the Responder")
-    cmd = "wpa_cli DPP_BOOTSTRAP_GEN type=qrcode info=%s" % info
+    cmd = "wpa_cli -iwlan0 DPP_BOOTSTRAP_GEN type=qrcode info=%s" % info
 
     if mac:
       cmd += " mac=%s" % mac
@@ -261,12 +272,6 @@
     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
@@ -283,13 +288,12 @@
 
         """
     self.log.info("Reading the contents of the URI of the Responder")
-    cmd = "wpa_cli DPP_BOOTSTRAP_GET_URI %s" % uri_id
+    cmd = "wpa_cli -iwlan0 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
@@ -302,7 +306,7 @@
           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
+    cmd = "wpa_cli -iwlan0 DPP_BOOTSTRAP_REMOVE %s" % uri_id
     result = device.adb.shell(cmd)
 
     # If URI was already flushed, ignore a failure here
@@ -390,14 +394,14 @@
     # Stop responder first
     self.stop_responder(device)
 
-    cmd = "wpa_cli set dpp_configurator_params guard=1 %s" % conf
+    cmd = "wpa_cli -iwlan0 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,
+    cmd = "wpa_cli -iwlan0 DPP_LISTEN %d role=configurator netrole=%s" % (freq,
                                                                   net_role)
     device.log.debug("Command used: %s" % cmd)
     result = self.helper_dev.adb.shell(cmd)
@@ -431,13 +435,13 @@
     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)
+    cmd = "wpa_cli -iwlan0 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.adb.shell("wpa_cli -iwlan0 set dpp_config_processing 2")
 
     device.log.info("Started responder in enrollee mode")
 
@@ -447,13 +451,13 @@
        Args:
            device: Device object
     """
-    result = device.adb.shell("wpa_cli DPP_STOP_LISTEN")
+    result = device.adb.shell("wpa_cli -iwlan0 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")
+    device.adb.shell("wpa_cli -iwlan0 set dpp_configurator_params")
+    device.adb.shell("wpa_cli -iwlan0 set dpp_config_processing 0")
     if flush:
-      device.adb.shell("wpa_cli flush")
+      device.adb.shell("wpa_cli -iwlan0 flush")
     device.log.info("Stopped responder")
 
   def start_dpp_as_initiator_configurator(self,
@@ -735,6 +739,191 @@
           self.forget_network(network_id),
           "Test network not deleted from configured networks.")
 
+  def stop_initiator(self, device, flush=False):
+    """Stop initiator on helper device
+
+       Args:
+           device: Device object
+    """
+    result = device.adb.shell("wpa_cli -iwlan0 DPP_STOP_LISTEN")
+    if "FAIL" in result:
+      asserts.fail("stop_initiator: Failed to stop initiator")
+    if flush:
+      device.adb.shell("wpa_cli -iwlan0 flush")
+    device.log.info("Stopped initiator")
+
+  def start_initiator_configurator(self,
+                                   device,
+                                   uri,
+                                   security=DPP_TEST_SECURITY_SAE):
+    """Start a responder on helper device
+
+        Args:
+            device: Device object
+            uri: peer uri
+            security: Security type: SAE or PSK
+
+        Returns:
+            ssid: SSID name of the network to be configured
+            uri_id: bootstrap id
+    """
+
+    self.log.info("Starting Initiator in Configurator mode")
+
+    use_psk = False
+
+    conf = " role=configurator"
+    conf += " conf=sta-"
+
+    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)
+      psk_encoded = binascii.b2a_hex(psk.encode()).decode()
+      self.log.debug("PSK = %s" % psk)
+    else:
+      password_encoded = binascii.b2a_hex(password.encode()).decode()
+      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
+
+    cmd = "wpa_cli -iwlan0 DPP_QR_CODE '%s'" % (uri)
+    self.log.info ("Command used: %s" % cmd)
+    result = self.helper_dev.adb.shell(cmd)
+    if "FAIL" in result:
+      asserts.fail(
+          "start_initiator_configurator: Failure. Command used: %s" % cmd)
+
+    uri_id = result
+    device.log.info("peer id = %s" % result)
+
+    cmd = "wpa_cli -iwlan0 DPP_AUTH_INIT peer=%s %s " % (result, conf)
+    self.log.info("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 initiator in configurator mode")
+    return ssid, uri_id
+
+  def start_dpp_as_responder_enrollee(self,
+                                      security,
+                                      curve):
+    """ Test Easy Connect (DPP) as responder enrollee.
+
+                1. Enable wifi, if needed
+                2. Start DPP as Responder - Enrollee on DUT
+                3. Receive the generated URI from DUT
+                3. Start DPP as Initiator - Configurator on helper device by passing the URI
+                4. Check if configuration received successfully on DUT
+                5. Remove the configuration.
+
+        Args:
+            security: Security type, a string "SAE" or "PSK"
+            curve: cryptography curve type, a string DPP_TEST_CRYPTOGRAPHY_CURVE_XXX
+    """
+    if not self.dut.droid.wifiIsEasyConnectEnrolleeResponderModeSupported():
+      self.log.warning("Easy Connect Enrollee responder mode is not supported on device!")
+      return
+
+    wutils.wifi_toggle_state(self.dut, True)
+
+    self.log.info("Starting DPP in Enrollee-Responder mode, curve: %s" % curve)
+
+    # Start DPP as Enrollee-Responder on DUT
+    self.dut.droid.startEasyConnectAsEnrolleeResponder("DPP_RESPONDER_TESTER", curve)
+
+    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:
+          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_URI_GENERATED:
+          self.dut.log.info(
+              "Generated URI %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_GENERATED_URI])
+          uri = dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_GENERATED_URI]
+
+          # Start DPP as an configurator-initiator for STA on helper device
+          result = self.start_initiator_configurator(self.helper_dev, uri, security=security)
+          continue
+      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:
+        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 initiator
+    self.stop_initiator(self.helper_dev, flush=True)
+
+    # Delete URI
+    if not result is None:
+      self.log.info("SSID: %s URI_ID: %s" % (result[0], result[1]))
+      self.del_uri(self.helper_dev, result[1])
+
+      # Check that the saved network is what we expect
+      asserts.assert_true(
+          self.check_network_config_saved(result[0], security, network_id),
+          "Could not find the expected network: %s" % result[0])
+      asserts.assert_true(
+          self.forget_network(network_id),
+          "Test network not deleted from configured networks.")
+    else:
+      asserts.fail("Failed to configure initiator")
+
   """ Tests Begin """
 
   @test_tracker_info(uuid="30893d51-2069-4e1c-8917-c8a840f91b59")
@@ -919,4 +1108,46 @@
     self.start_dpp_as_initiator_configurator(
       security=self.DPP_TEST_SECURITY_PSK, use_mac=True, r2_auth_error=True)
 
+  @test_tracker_info(uuid="608c8d47-b9ed-4668-a438-cf5035d27818")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_responder_enrollee_with_psk_passphrase_curve_prime256v1(self):
+    self.start_dpp_as_responder_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        curve=self.DPP_TEST_CRYPTOGRAPHY_CURVE_PRIME256V1)
+
+  @test_tracker_info(uuid="51d47a54-e19e-4513-b6e8-161e786db0b0")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_responder_enrollee_with_sae_curve_prime256v1(self):
+    self.start_dpp_as_responder_enrollee(
+        security=self.DPP_TEST_SECURITY_SAE,
+        curve=self.DPP_TEST_CRYPTOGRAPHY_CURVE_PRIME256V1)
+
+  @test_tracker_info(uuid="f4ede61b-2cee-4ff0-b9d6-3dca9245021b")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_responder_enrollee_with_psk_passphrase_curve_secp384r1(self):
+    self.start_dpp_as_responder_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        curve=self.DPP_TEST_CRYPTOGRAPHY_CURVE_SECP384R1)
+
+  @test_tracker_info(uuid="57855cba-9cf2-4837-ae77-3dc78bf8b3b5")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_responder_enrollee_with_sae_curve_secp384r1(self):
+    self.start_dpp_as_responder_enrollee(
+        security=self.DPP_TEST_SECURITY_SAE,
+        curve=self.DPP_TEST_CRYPTOGRAPHY_CURVE_SECP384R1)
+
+  @test_tracker_info(uuid="eb9c22a0-f17e-4985-b1b0-fdd3871d29b7")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_responder_enrollee_with_psk_passphrase_curve_secp521r1(self):
+    self.start_dpp_as_responder_enrollee(
+        security=self.DPP_TEST_SECURITY_PSK_PASSPHRASE,
+        curve=self.DPP_TEST_CRYPTOGRAPHY_CURVE_SECP521R1)
+
+  @test_tracker_info(uuid="2677e549-a37a-42f6-8fab-c9a68fcf15f9")
+  @WifiBaseTest.wifi_test_wrap
+  def test_dpp_as_responder_enrollee_with_sae_curve_secp521r1(self):
+    self.start_dpp_as_responder_enrollee(
+        security=self.DPP_TEST_SECURITY_SAE,
+        curve=self.DPP_TEST_CRYPTOGRAPHY_CURVE_SECP521R1)
+
 """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py b/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
index 2227f3e..6eea512 100644
--- a/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
@@ -13,12 +13,6 @@
 #   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_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
@@ -43,9 +37,8 @@
         super().setup_class()
 
         self.dut = self.android_devices[0]
-        wutils.wifi_test_device_init(self.dut)
         req_params = (
-            "attn_vals",
+            "roaming_attn",
             # Expected time within which roaming should finish, in seconds.
             "roam_interval",
             "ca_cert",
@@ -54,6 +47,7 @@
             "eap_identity",
             "eap_password",
             "device_password",
+            "wifi6_models",
             "radius_conf_2g",
             "radius_conf_5g")
         self.unpack_userparams(req_params)
@@ -64,13 +58,22 @@
                 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.bssid_2g_a = self.ent_network_2g_a[WifiEnums.BSSID_KEY.lower()]
-        self.bssid_2g_b = self.ent_network_2g_b[WifiEnums.BSSID_KEY.lower()]
         self.ent_roaming_ssid = self.ent_network_2g_a[WifiEnums.SSID_KEY]
-        self.bssid_a = self.bssid_2g_a
-        self.bssid_b = self.bssid_2g_b
+        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),
@@ -102,15 +105,17 @@
         }
         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")
+        wutils.set_attns(self.attenuators, "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")
+        wutils.set_attns(self.attenuators, "default")
 
     def setup_test(self):
         super().setup_test()
@@ -125,23 +130,7 @@
         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
+        wutils.set_attns(self.attenuators, "default")
 
     def trigger_roaming_and_validate(self, attn_val_name, expected_con):
         """Sets attenuators to trigger roaming and validate the DUT connected
@@ -152,9 +141,9 @@
             expected_con: The expected info of the network to we expect the DUT
                 to roam to.
         """
-        self.set_attns(attn_val_name)
+        wutils.set_attns_steps(
+            self.attenuators, attn_val_name, self.roaming_attn)
         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()
@@ -184,13 +173,20 @@
             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.set_attns_steps(
+            self.attenuators, "AP1_on_AP2_off", self.roaming_attn)
+        wutils.connect_to_wifi_network(self.dut, config)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         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.trigger_roaming_and_validate("AP1_off_AP2_on", expected_con_to_b)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         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)
+        self.trigger_roaming_and_validate("AP1_on_AP2_off", expected_con_to_a)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     """ Tests Begin """
 
diff --git a/acts_tests/tests/google/wifi/WifiEnterpriseTest.py b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
index f31afd0..f9a7cab 100644
--- a/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
@@ -14,9 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import pprint
-import random
 import time
+import acts.utils
 
 from acts import asserts
 from acts import signals
@@ -25,6 +24,7 @@
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
+DEFAULT_TIMEOUT = 10
 
 # EAP Macros
 EAP = WifiEnums.Eap
@@ -36,21 +36,19 @@
 class WifiEnterpriseTest(WifiBaseTest):
     def setup_class(self):
         super().setup_class()
+        self.enable_packet_log = True
 
         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",
+            "ca_cert", "client_cert", "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")
+            "invalid_client_key", "device_password", "radius_conf_2g",
+            "radius_conf_5g", "radius_conf_pwd", "wifi6_models")
         self.unpack_userparams(required_userparam_names,
                                roaming_consortium_ids=None,
                                plmn=None,
@@ -62,10 +60,27 @@
                 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,)
+                radius_conf_pwd=self.radius_conf_pwd,
+                wpa_network=True,
+                ap_count=2,
+            )
+        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,
+                wpa_network=True,
+                ap_count=2,
+            )
         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"]
+        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"]
 
         # Default configs for EAP networks.
         self.config_peap0 = {
@@ -117,29 +132,6 @@
             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)
 
@@ -161,6 +153,9 @@
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
         self.dut.droid.wifiStopTrackingStateChange()
+        # Turn off airplane mode
+        acts.utils.force_airplane_mode(self.dut, False)
+        wutils.set_attns(self.attenuators, "default")
 
     """Helper Functions"""
 
@@ -195,8 +190,9 @@
             field.
         """
         negative_config = dict(config)
-        if negative_config in [self.config_sim, self.config_aka,
-                               self.config_aka_prime]:
+        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
@@ -231,32 +227,7 @@
         }
         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):
+    def eap_connect_toggle_wifi(self, config, *args):
         """Connects to an enterprise network, toggles wifi state and ensures
         that the device reconnects.
 
@@ -271,7 +242,71 @@
         """
         ad = args[0]
         wutils.wifi_connect(ad, config)
-        wutils.toggle_wifi_and_wait_for_reconnection(ad, config, num_of_tries=5)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+        wutils.toggle_wifi_and_wait_for_reconnection(ad,
+                                                     config,
+                                                     num_of_tries=5)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+
+    def toggle_out_of_range_stress(self, stress_count=3):
+        """toggle_out_of_range_stress."""
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Current network: {}".format(current_network))
+        for count in range(stress_count):
+            # move the DUT out of range
+            self.attenuators[0].set_atten(95)
+            self.attenuators[1].set_atten(95)
+            self.attenuators[2].set_atten(95)
+            self.attenuators[3].set_atten(95)
+            time.sleep(20)
+            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))
+                time.sleep(DEFAULT_TIMEOUT)
+            finally:
+                self.dut.droid.wifiLockRelease()
+            # move the DUT back in range
+            wutils.set_attns(self.attenuators, "default")
+            time.sleep(30)
+            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 disconnected to network {}".format(current_network))
+                time.sleep(DEFAULT_TIMEOUT)
+            finally:
+                self.dut.droid.wifiLockRelease()
+
+    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(10)  #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
 
     """ Tests """
 
@@ -293,79 +328,111 @@
             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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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
+        config[
+            WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
         wutils.wifi_connect(self.dut, config)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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
+        config[
+            WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
         wutils.wifi_connect(self.dut, config)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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
+        config[
+            WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
         wutils.wifi_connect(self.dut, config)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     # EAP connect negative tests
     """ Test connecting to enterprise networks.
@@ -377,11 +444,26 @@
         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="b7fb8517-5d52-468e-890a-40ea24129bf1")
+    def test_network_selection_status_wpa2_eap_tls_invalid_cert(self):
+        config = self.gen_negative_eap_configs(self.config_tls)
+        try:
+            wutils.connect_to_wifi_network(self.dut, config)
+            asserts.fail(
+                "WPA2 EAP TLS worked with invalid cert. Expected to fail.")
+        except:
+            asserts.assert_true(
+                self.dut.droid.wifiIsNetworkTemporaryDisabledForNetwork(config),
+                "WiFi network is not temporary disabled.")
+            asserts.explicit_pass(
+                "Connection failed with correct network selection status.")
+
     @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)
@@ -426,7 +508,8 @@
     @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[
+            WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
         config = self.gen_negative_eap_configs(config)
         self.eap_negative_connect_logic(config, self.dut)
 
@@ -440,7 +523,8 @@
     @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[
+            WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
         config = self.gen_negative_eap_configs(config)
         self.eap_negative_connect_logic(config, self.dut)
 
@@ -454,7 +538,8 @@
     @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[
+            WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.MSCHAPV2.value
         config = self.gen_negative_eap_configs(config)
         self.eap_negative_connect_logic(config, self.dut)
 
@@ -485,6 +570,7 @@
             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)
@@ -526,7 +612,8 @@
     @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
+        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")
@@ -538,7 +625,8 @@
     @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
+        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")
@@ -550,7 +638,8 @@
     @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
+        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")
@@ -559,195 +648,144 @@
         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
+    # Airplane mode on with wifi connect tests
     """ Test connecting to enterprise networks of different authentication
-        types with passpoint support.
+        types after airplane mode on.
 
         The authentication types tested are:
-            EAP-TLS
-            EAP-TTLS with MSCHAPV2 as phase2.
+            EAP-SIM
+            EAP-AKA
+            EAP-AKA_PRIME
 
         Procedures:
             For each enterprise wifi network
-            1. Connect to the network.
-            2. Send a GET request to a website and check response.
+            1. Turn on Airplane mode
+            2. Toggle wifi..
+            3. Ensure that the device connects to the enterprise network.
 
         Expect:
             Successful connection and Internet access through the enterprise
-            networks with passpoint support.
+            networks with Airplane mode on.
     """
-    @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="54b96a6c-f366-421c-9a72-80d7ee8fac8f")
+    def test_eap_connect_with_config_sim_airplane_on(self):
+        self.log.info("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)
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_connect(self.dut, self.config_sim)
+        self.log.info("Toggling Airplane mode OFF")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, False),
+            "Can not turn OFF airplane mode: %s" % self.dut.serial)
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+          self.dut, self.config_sim[WifiEnums.SSID_KEY])
 
-    @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="344f63f6-7c99-4507-8036-757f9f911d20")
+    def test_eap_connect_with_config_aka_airplane_on(self):
+        self.log.info("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)
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_connect(self.dut, self.config_aka)
+        self.log.info("Toggling Airplane mode OFF")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, False),
+            "Can not turn OFF airplane mode: %s" % self.dut.serial)
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+          self.dut, self.config_aka[WifiEnums.SSID_KEY])
 
-    @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="5502b8c8-89d7-4ce9-afee-cae50e71f5f4")
+    def test_eap_connect_with_config_aka_prime_airplane_on(self):
+        self.log.info("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)
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.wifi_connect(self.dut, self.config_aka_prime)
+        self.log.info("Toggling Airplane mode OFF")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, False),
+            "Can not turn OFF airplane mode: %s" % self.dut.serial)
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+          self.dut, self.config_aka_prime[WifiEnums.SSID_KEY])
 
-    @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="360a6fec-f4ee-4ecd-9b15-e836c977d6db")
+    def test_connect_eap_sim_network_out_of_range_back(self):
+        """Test connecting to enterprise networks to do out of range
+        then back in range 3 times
+         1. Connecting EAP-SIM network
+         2. Move DUT out of range then back in range 3 times
+         3. Check that device is connected to network.
+        """
+        wutils.wifi_connect(self.dut, self.config_sim)
+        self.toggle_out_of_range_stress()
 
-    @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)
+    @test_tracker_info(uuid="9fb71afb-5599-4ca1-b458-09752c40bb0d")
+    def test_eap_sim_network_out_of_range_back_airplane(self):
+        """Test connecting to enterprise networks with airplne mode on
+        to do out of range then back in range 3 times
+         1. Turn on airplane mode
+         2. Connecting EAP-SIM network
+         3. Move DUT out of range then back in range 3 times
+         4. Check that device is connected to network.
+        """
+        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.log.debug("Toggling WiFi mode ON")
+        wutils.wifi_toggle_state(self.dut, True)
+        time.sleep(DEFAULT_TIMEOUT)
+        wutils.wifi_connect(self.dut, self.config_sim)
+        self.toggle_out_of_range_stress()
 
-    # Passpoint connect negative tests
-    """ Test connecting to enterprise networks.
+    @test_tracker_info(uuid="9e899c55-1a62-498c-bbf1-e9472e42e84f")
+    def test_eap_sim_network_reboot(self):
+        """Test connecting to enterprise networks with airplne mode on
+        to do out of range then back in range 3 times
+         1. Connecting EAP-SIM network
+         3. Check that device is connected to network after reboot.
+        """
+        self.dut.droid.disableDevicePassword(self.device_password)
+        wutils.wifi_connect(self.dut, self.config_sim)
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Current network: {}".format(current_network))
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        self.check_connection(self.config_sim)
 
-        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)
+    @test_tracker_info(uuid="8e7465fb-5b16-4abb-92d8-a2c79355e377")
+    def test_connect_to_EAP_SIM_network_switch_to_WPA2(self):
+        """Test connecting PSK's AP1 and one EAP AP2 network switch test
+        1. Connect to a PSK's AP1 before connect to EAP-SIM AP2 network.
+        2. Out of PSK's AP1 range.
+        3. Connect to EAP-SIM network, then in AP1 range to switch WPA2-PSK network.
+        """
+        attn1 = self.attenuators[0]
+        attn2 = self.attenuators[2]
+        if "OpenWrtAP" in self.user_params:
+            attn2 = self.attenuators[1]
+        ap1_network = self.config_sim
+        ap2_network = self.reference_networks[1]["2g"]
+        attn1.set_atten(0)
+        attn2.set_atten(0)
+        wutils.connect_to_wifi_network(self.dut, ap2_network)
+        #Enable EAP network signal
+        attn2.set_atten(95)
+        time.sleep(DEFAULT_TIMEOUT)
+        wutils.connect_to_wifi_network(self.dut, ap1_network)
+        current_network = self.dut.droid.wifiGetConnectionInfo()
+        self.log.info("Current network: {}".format(current_network))
+        #Enable SSID1 network signal
+        attn1.set_atten(95)
+        attn2.set_atten(0)
+        time.sleep(20)
+        self.check_connection(ap2_network)
diff --git a/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
index 9dd73c3..1435dab 100644
--- a/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
+++ b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
@@ -14,17 +14,9 @@
 #   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
 import acts_contrib.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_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
@@ -37,41 +29,39 @@
     * 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(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)
 
-        asserts.assert_true(
-            len(self.reference_networks) > 0,
-            "Need at least one reference network with psk.")
         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 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)
-
     def teardown_class(self):
         wutils.reset_wifi(self.dut)
         if "AccessPoint" in self.user_params:
@@ -160,3 +150,23 @@
 
         """
         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
index 29a131d..ef24d4d 100644
--- a/acts_tests/tests/google/wifi/WifiIFSTwTest.py
+++ b/acts_tests/tests/google/wifi/WifiIFSTwTest.py
@@ -87,6 +87,7 @@
         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()
@@ -96,6 +97,7 @@
         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)
diff --git a/acts_tests/tests/google/wifi/WifiIOTTest.py b/acts_tests/tests/google/wifi/WifiIOTTest.py
index e69de29..973fc08 100644
--- a/acts_tests/tests/google/wifi/WifiIOTTest.py
+++ 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_contrib.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import asserts
+from acts.test_decorators import test_tracker_info
+from acts_contrib.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
index c300804..9669cdd 100644
--- a/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
+++ b/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
@@ -88,10 +88,12 @@
             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)
@@ -100,10 +102,6 @@
         if "iperf_server_address" 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 connect_to_wifi_network(self, network):
diff --git a/acts_tests/tests/google/wifi/WifiIOTtpeTest.py b/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
index 98729f2..8244fb8 100644
--- a/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
+++ b/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
@@ -67,10 +67,12 @@
             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)
@@ -79,10 +81,6 @@
         if "iperf_server_address" 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 connect_to_wifi_network(self, network):
diff --git a/acts_tests/tests/google/wifi/WifiLinkProbeTest.py b/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
index 09d1234..8dd1a97 100644
--- a/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
+++ b/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
@@ -47,34 +47,27 @@
 
         if "AccessPoint" in self.user_params:
             self.legacy_configure_ap_and_start()
-        self.configure_packet_capture()
+        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)
-        self.pcap_procs = wutils.start_pcap(
-            self.packet_capture, 'dual', self.test_name)
 
     def teardown_test(self):
+        super().teardown_test()
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
         wutils.reset_wifi(self.dut)
 
-    def on_pass(self, test_name, begin_time):
-        wutils.stop_pcap(self.packet_capture, self.pcap_procs, True)
-
-    def on_fail(self, test_name, begin_time):
-        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.cat_adb_log(test_name, begin_time)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
diff --git a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
index 2c5e8ee..e7fd9fa 100644
--- a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
+++ b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
@@ -66,7 +66,8 @@
         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"]
+        req_params = ["sta_sta_supported_models", "dbs_supported_models",
+                      "support_one_factory_mac_address", "roaming_attn"]
         opt_param = [
             "open_network", "reference_networks", "wep_networks"
         ]
@@ -79,7 +80,14 @@
         self.configure_packet_capture()
 
         if "AccessPoint" in self.user_params:
-            self.legacy_configure_ap_and_start(wep_network=True, ap_count=2)
+            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,
@@ -91,7 +99,11 @@
         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()
+        if self.dut.model in self.support_one_factory_mac_address:
+            self.soft_ap_factory_mac = (self.dut.droid
+                                        .wifigetFactorymacAddresses()[0])
+        else:
+            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"]
@@ -102,15 +114,18 @@
         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)
 
@@ -256,11 +271,59 @@
 
     def get_soft_ap_mac_address(self):
         """Gets the current MAC address being used for SoftAp."""
+        if self.dut.model in self.sta_sta_supported_models:
+            out = self.dut.adb.shell("ifconfig wlan2")
+            return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
         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"""
 
 
@@ -400,7 +463,7 @@
             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_differnet_APs(self):
+    def test_mac_randomization_different_APs(self):
         """Verify randomization using two different APs.
 
         Steps:
@@ -485,6 +548,9 @@
         """
         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)
@@ -521,6 +587,17 @@
         time.sleep(SHORT_TIMEOUT)
         network = self.wpapsk_5g
         rand_mac = self.connect_to_network_and_verify_mac_randomization(network)
+        pcap_fname_bflink = '%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_bflink = rdpcap(pcap_fname_bflink)
+        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets_bflink)
+        self.verify_mac_is_found_in_pcap(rand_mac, packets_bflink)
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+        time.sleep(SHORT_TIMEOUT)
         wutils.send_link_probes(self.dut, 3, 3)
         pcap_fname = '%s_%s.pcap' % \
             (self.pcap_procs[hostapd_constants.BAND_5G][1],
@@ -529,7 +606,7 @@
         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)
+        self.verify_mac_is_found_in_pcap(rand_mac, packets)
 
     @test_tracker_info(uuid="1c2cc0fd-a340-40c4-b679-6acc5f526451")
     def test_check_mac_in_wifi_scan(self):
@@ -553,3 +630,46 @@
              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.
+        """
+        asserts.skip_if(not self.dut.droid.isSdkAtLeastS(),
+                        "This feature is only supported on S and later.")
+
+        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
index 98bfcce..902272c 100644
--- a/acts_tests/tests/google/wifi/WifiManagerTest.py
+++ b/acts_tests/tests/google/wifi/WifiManagerTest.py
@@ -15,6 +15,7 @@
 #   limitations under the License.
 
 import itertools
+import json
 import pprint
 import queue
 import time
@@ -29,6 +30,8 @@
 from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
 from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.wifi_constants import\
+    COEX_BAND, COEX_CHANNEL, COEX_POWER_CAP_DBM, KEY_COEX_UNSAFE_CHANNELS, KEY_COEX_RESTRICTIONS
 
 WifiEnums = wutils.WifiEnums
 # Default timeout used for reboot, toggle WiFi and Airplane mode,
@@ -47,6 +50,10 @@
       network.
     """
 
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = True
+
     def setup_class(self):
         super().setup_class()
 
@@ -63,13 +70,18 @@
         req_params = []
         opt_param = [
             "open_network", "reference_networks", "iperf_server_address",
-            "wpa_networks", "wep_networks", "iperf_server_port"
+            "wpa_networks", "wep_networks", "iperf_server_port",
+            "coex_unsafe_channels", "coex_restrictions", "wifi6_models"
         ]
         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,
@@ -80,27 +92,26 @@
         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 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)
-        if self.dut_client:
-            self.dut_client.take_bug_report(test_name, begin_time)
+        for ad in self.android_devices:
+            wutils.reset_wifi(ad)
+        self.log.debug("Toggling Airplane mode OFF")
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, False),
+            "Can not turn airplane mode off: %s" % self.dut.serial)
 
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
@@ -234,6 +245,8 @@
                        (network_ssid, connect_ssid))
         if connect_ssid != network_ssid:
             return False
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         return True
 
     def run_iperf_client(self, params):
@@ -377,6 +390,8 @@
         if not reconnect:
             raise signals.TestFailure("Device did not connect to the correct"
                                       " network after toggling WiFi.")
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     def helper_reconnect_toggle_airplane(self):
         """Connect to multiple networks, turn on/off Airplane moce, then
@@ -400,8 +415,10 @@
         if not reconnect:
             raise signals.TestFailure("Device did not connect to the correct"
                                       " network after toggling Airplane mode.")
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
-    def helper_reboot_configstore_reconnect(self):
+    def helper_reboot_configstore_reconnect(self, lock_screen=False):
         """Connect to multiple networks, reboot then reconnect to previously
            connected network.
 
@@ -414,6 +431,7 @@
 
         """
         network_list = self.connect_multiple_networks(self.dut)
+        network_list = self.dut.droid.wifiGetConfiguredNetworks()
         self.dut.reboot()
         time.sleep(DEFAULT_TIMEOUT)
         self.check_configstore_networks(network_list)
@@ -421,6 +439,9 @@
         reconnect_to = self.get_enabled_network(network_list[BAND_2GHZ],
                                                 network_list[BAND_5GHZ])
 
+        if lock_screen:
+            self.dut.droid.wakeLockRelease()
+            self.dut.droid.goToSleepNow()
         reconnect = self.connect_to_wifi_network_with_id(
             reconnect_to[WifiEnums.NETID_KEY],
             reconnect_to[WifiEnums.SSID_KEY])
@@ -428,6 +449,8 @@
             raise signals.TestFailure(
                 "Device failed to reconnect to the correct"
                 " network after reboot.")
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     def helper_toggle_wifi_reboot_configstore_reconnect(self):
         """Connect to multiple networks, disable WiFi, reboot, then
@@ -447,6 +470,7 @@
         self.log.debug("Toggling wifi OFF")
         wutils.wifi_toggle_state(self.dut, False)
         time.sleep(DEFAULT_TIMEOUT)
+        network_list = self.dut.droid.wifiGetConfiguredNetworks()
         self.dut.reboot()
         time.sleep(DEFAULT_TIMEOUT)
         self.log.debug("Toggling wifi ON")
@@ -462,6 +486,8 @@
             msg = ("Device failed to reconnect to the correct network after"
                    " toggling WiFi and rebooting.")
             raise signals.TestFailure(msg)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     def helper_toggle_airplane_reboot_configstore_reconnect(self):
         """Connect to multiple networks, enable Airplane mode, reboot, then
@@ -483,6 +509,7 @@
             acts.utils.force_airplane_mode(self.dut, True),
             "Can not turn on airplane mode on: %s" % self.dut.serial)
         time.sleep(DEFAULT_TIMEOUT)
+        network_list = self.dut.droid.wifiGetConfiguredNetworks()
         self.dut.reboot()
         time.sleep(DEFAULT_TIMEOUT)
         self.log.debug("Toggling Airplane mode OFF")
@@ -500,6 +527,8 @@
             msg = ("Device failed to reconnect to the correct network after"
                    " toggling Airplane mode and rebooting.")
             raise signals.TestFailure(msg)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     def verify_traffic_between_devices(self,dest_device,src_device,num_of_tries=2):
         """Test the clients and DUT can ping each other.
@@ -516,6 +545,17 @@
         else:
             asserts.fail("Ping to %s from %s failed" % (src_device.serial, dest_device))
 
+    def ping_public_gateway_ip(self):
+        """Ping 8.8.8.8"""
+        try:
+            ping_result = self.dut.adb.shell('ping -w 5 8.8.8.8')
+            if '0%' in ping_result:
+                self.dut.log.info('Ping success')
+            return True
+        except:
+            self.dut.log.error('Faild to ping public gateway 8.8.8.8')
+            return False
+
     """Tests"""
 
     @test_tracker_info(uuid="525fc5e3-afba-4bfd-9a02-5834119e3c66")
@@ -722,6 +762,61 @@
         self.turn_location_on_and_scan_toggle_on()
         self.helper_reconnect_toggle_airplane()
 
+    @test_tracker_info(uuid="52b89a47-f260-4343-922d-fbeb4d8d2b63")
+    def test_reconnect_toggle_wifi_on_with_airplane_on(self):
+        """Connect to multiple networks, turn on airplane mode, turn on wifi,
+        then reconnect a previously connected network.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Turn ON Airplane mode.
+        4. Turn ON WiFi.
+        5. Reconnect to the a previously connected 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)
+        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)
+        self.log.debug("Toggling wifi ON")
+        wutils.wifi_toggle_state(self.dut, True)
+        time.sleep(DEFAULT_TIMEOUT)
+        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.")
+
+    @test_tracker_info(uuid="2dddc734-e9f6-4d30-9c2d-4368e721a350")
+    def test_verify_airplane_mode_on_with_wifi_disabled(self):
+        """Connect to multiple networks, turn on airplane mode, turn off Wi-Fi,
+        then make sure there is no internet.
+
+        Steps:
+        1. Connect to a 2GHz network.
+        2. Connect to a 5GHz network.
+        3. Turn ON Airplane mode.
+        4. Turn OFF WiFi.
+        5. Ping to make sure there is no internet
+        """
+        connect_2g_data = self.get_connection_data(self.dut, self.wpapsk_2g)
+        connect_5g_data = self.get_connection_data(self.dut, self.wpapsk_5g)
+        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)
+        self.log.debug("Toggling Wi-Fi OFF")
+        wutils.wifi_toggle_state(self.dut, False)
+        time.sleep(DEFAULT_TIMEOUT)
+        if self.ping_public_gateway_ip():
+            raise signals.TestFailure("Device has internet after"
+                                             " toggling WiFi off.")
+
     @test_tracker_info(uuid="3d041c12-05e2-46a7-ab9b-e3f60cc735db")
     def test_reboot_configstore_reconnect(self):
         """Connect to multiple networks, reboot then reconnect to previously
@@ -826,15 +921,47 @@
         self.turn_location_on_and_scan_toggle_on()
         self.helper_toggle_airplane_reboot_configstore_reconnect()
 
+    @test_tracker_info(uuid="342c13cb-6508-4942-bee3-07c5d20d92a5")
+    def test_reboot_configstore_reconnect_with_screen_lock(self):
+        """Verify device can re-connect to configured networks after reboot.
+
+        Steps:
+        1. Connect to 2G and 5G networks.
+        2. Reboot device
+        3. Verify device connects to 1 network automatically.
+        4. Lock screen and verify device can connect to the other network.
+        """
+        self.helper_reboot_configstore_reconnect(lock_screen=True)
+
+    @test_tracker_info(uuid="7e6050d9-79b1-4726-80cf-686bb99b8945")
+    def test_connect_to_5g_after_reboot_without_unlock(self):
+        """Connect to 5g network afer reboot without unlock.
+
+        Steps:
+        1. Reboot device and lock screen
+        2. Connect to 5G network and verify it works.
+        """
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        wutils.connect_to_wifi_network(self.dut, self.wpapsk_5g)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+
     @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))
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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))
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @test_tracker_info(uuid="b9fbc13a-47b4-4f64-bd2c-e5a3cb24ab2f")
     def test_tdls_supported(self):
@@ -862,6 +989,8 @@
         """
         wutils.wifi_connect(self.dut, self.open_network_2g)
         self.get_energy_info()
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @test_tracker_info(uuid="2622c253-defc-4a35-93a6-ca9d29a8238c")
     def test_connect_to_wep_2g(self):
@@ -892,6 +1021,8 @@
         2. Connect to the network and validate internet connection.
         """
         wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["2g"])
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @test_tracker_info(uuid="612c3c31-a4c5-4014-9a2d-3f4bcc20c0d7")
     def test_connect_to_wpa_5g(self):
@@ -902,6 +1033,8 @@
         2. Connect to the network and validate internet connection.
         """
         wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["5g"])
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @test_tracker_info(uuid="2a617fb4-1d8e-44e9-a500-a5456e1df83f")
     def test_connect_to_2g_can_be_pinged(self):
@@ -913,7 +1046,11 @@
         3. Check DUT can be pinged by another device
         """
         wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["2g"])
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         wutils.connect_to_wifi_network(self.dut_client, self.wpa_networks[0]["2g"])
+        wutils.verify_11ax_wifi_connection(
+            self.dut_client, self.wifi6_models, "wifi6_ap" in self.user_params)
         self.verify_traffic_between_devices(self.dut,self.dut_client)
 
     @test_tracker_info(uuid="94bdd657-649b-4a2c-89c3-3ec6ba18e08e")
@@ -926,7 +1063,11 @@
         3. Check DUT can be pinged by another device
         """
         wutils.connect_to_wifi_network(self.dut, self.wpa_networks[0]["5g"])
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         wutils.connect_to_wifi_network(self.dut_client, self.wpa_networks[0]["5g"])
+        wutils.verify_11ax_wifi_connection(
+            self.dut_client, self.wifi6_models, "wifi6_ap" in self.user_params)
         self.verify_traffic_between_devices(self.dut,self.dut_client)
 
     @test_tracker_info(uuid="d87359aa-c4da-4554-b5de-8e3fa852a6b0")
@@ -979,6 +1120,31 @@
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
 
+    @test_tracker_info(uuid="25e8dd62-ae9f-46f7-96aa-030fee95dfda")
+    def test_wifi_saved_network_reset(self):
+        """Verify DUT can reset Wi-Fi saved network list after add a network.
+
+        Steps:
+        1. Connect to a 2GHz network
+        2. Reset the Wi-Fi saved network
+        3. Verify the saved network has been clear
+        """
+        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)
+        self.dut.droid.wifiFactoryReset()
+        configured_networks = self.dut.droid.wifiGetConfiguredNetworks()
+        for nw in configured_networks:
+            asserts.assert_true(
+                nw[WifiEnums.BSSID_KEY] != ssid,
+                "Found saved network %s in configured networks." % ssid)
+
     @test_tracker_info(uuid="402cfaa8-297f-4865-9e27-6bab6adca756")
     def test_reboot_wifi_and_bluetooth_on(self):
         """Toggle WiFi and bluetooth ON then reboot """
@@ -1022,6 +1188,8 @@
         """
         network = self.open_network_5g
         wutils.connect_to_wifi_network(self.dut, network)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         info = self.dut.droid.wifiGetConnectionInfo()
         network_id = info[WifiEnums.NETID_KEY]
         self.dut.log.info("Disable auto join on network")
@@ -1033,3 +1201,92 @@
                                     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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+
+    def coex_unsafe_channel_key(self, unsafe_channel):
+        if COEX_POWER_CAP_DBM in unsafe_channel:
+            return (unsafe_channel[COEX_BAND], unsafe_channel[COEX_CHANNEL],
+                    unsafe_channel[COEX_POWER_CAP_DBM])
+        return (unsafe_channel[COEX_BAND], unsafe_channel[COEX_CHANNEL])
+
+    @test_tracker_info(uuid="78558b30-3792-4a1f-bb56-34bbbbce6ac8")
+    def test_set_get_coex_unsafe_channels(self):
+        """
+        Set the unsafe channels to avoid for coex, then retrieve the active values and compare to
+        values set. If the default algorithm is enabled, then ensure that the active values are
+        unchanged.
+
+        Steps:
+        1. Register a coex callback and listen for the update event to get the current coex values.
+        2. Create list of coex unsafe channels and restrictions
+
+            coex_unsafe_channels format:
+                [
+                    {
+                        "band": <"24_GHZ" or "5_GHZ">
+                        "channel" : <Channel number>
+                        (Optional) "powerCapDbm" : <Power Cap in Dbm>
+                    }
+                    ...
+                ]
+
+            coex_restrictions format:
+                [
+                    (Optional) "WIFI_DIRECT",
+                    (Optional) "SOFTAP",
+                    (Optional) "WIFI_AWARE"
+                ]
+        3. Set these values as the active values and listen for the update event.
+        4. If the default algorithm is enabled, expect to not get the update event. If it is
+           disabled, compare the updated values and see if they are the same as the provided values.
+        5. Restore the previous values if the test values were set.
+        """
+        asserts.skip_if(not self.dut.droid.isSdkAtLeastS(),
+                        "Require SDK at least S to use wifi coex apis.")
+        self.dut.ed.clear_all_events()
+
+        # Register a coex callback to start receiving coex events
+        self.dut.droid.wifiRegisterCoexCallback()
+        try:
+            # Wait for the immediate callback from registering and store the current values
+            event = self.dut.ed.pop_event("WifiManagerCoexCallback#onCoexUnsafeChannelsChanged", 5)
+        except queue.Empty:
+            asserts.fail("Coex callback event not received after registering.")
+        prev_unsafe_channels = sorted(json.loads(event["data"][KEY_COEX_UNSAFE_CHANNELS]),
+                                      key=self.coex_unsafe_channel_key)
+        prev_restrictions = sorted(json.loads(event["data"][KEY_COEX_RESTRICTIONS]))
+
+        # Set new values for coex unsafe channels
+        test_unsafe_channels = sorted(self.coex_unsafe_channels,
+                                      key=self.coex_unsafe_channel_key)
+        test_restrictions = sorted(self.coex_restrictions)
+        self.dut.droid.wifiSetCoexUnsafeChannels(test_unsafe_channels, test_restrictions)
+        try:
+            # Wait for the callback from setting the coex unsafe channels
+            event = self.dut.ed.pop_event("WifiManagerCoexCallback#onCoexUnsafeChannelsChanged", 5)
+            # Callback received. This should be expected only if default algo is disabled.
+            asserts.assert_false(self.dut.droid.wifiIsDefaultCoexAlgorithmEnabled(),
+                                "Default algo was enabled but Coex callback received after"
+                                " setCoexUnsafeChannels")
+            curr_unsafe_channels = sorted(json.loads(event["data"][KEY_COEX_UNSAFE_CHANNELS]),
+                                          key=self.coex_unsafe_channel_key)
+            curr_restrictions = sorted(json.loads(event["data"][KEY_COEX_RESTRICTIONS]))
+            # Compare the current values with the set values
+            asserts.assert_true(curr_unsafe_channels == test_unsafe_channels,
+                                "default coex algorithm disabled but current unsafe channels "
+                                + str(curr_unsafe_channels)
+                                + " do not match the set values " + str(test_unsafe_channels))
+            asserts.assert_true(curr_restrictions == test_restrictions,
+                                "default coex algorithm disabled but current restrictions "
+                                + str(curr_restrictions)
+                                + " do not match the set values " + str(test_restrictions))
+            # Restore the previous values
+            self.dut.droid.wifiSetCoexUnsafeChannels(prev_unsafe_channels, prev_restrictions)
+        except queue.Empty:
+            # Callback not received. This should be expected only if the default algo is enabled.
+            asserts.assert_true(self.dut.droid.wifiIsDefaultCoexAlgorithmEnabled(),
+                                "Default algo was disabled but Coex callback not received after"
+                                " setCoexUnsafeChannels")
+
+        self.dut.droid.wifiUnregisterCoexCallback()
diff --git a/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py b/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
index 5e21cc4..b1508be 100644
--- a/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
+++ b/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
@@ -14,21 +14,17 @@
 #   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_contrib.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_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
 from acts_contrib.test_utils.wifi import wifi_constants
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
 
 WifiEnums = wutils.WifiEnums
 
@@ -45,6 +41,9 @@
     * 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()
@@ -53,7 +52,7 @@
         wutils.wifi_test_device_init(self.dut)
         req_params = []
         opt_param = [
-            "open_network", "reference_networks"
+            "open_network", "reference_networks", "sta_concurrency_supported_models"
         ]
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
@@ -61,6 +60,10 @@
         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,
@@ -69,64 +72,46 @@
         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"]
+        if "sta_concurrency_supported_models" in self.user_params:
+            self.sta_concurrency_supported_models = \
+                    self.dut.model in self.sta_concurrency_supported_models
+        else:
+            self.sta_concurrency_supported_models = False
 
     def setup_test(self):
+        super().setup_test()
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
         self.remove_approvals()
-        self.clear_deleted_ephemeral_networks()
+        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 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)
-
     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_deleted_ephemeral_networks(self):
-        self.dut.log.debug("Clearing deleted ephemeral networks")
+    def clear_user_disabled_networks(self):
+        self.dut.log.debug("Clearing user disabled networks")
         self.dut.adb.shell(
-            "cmd wifi clear-deleted-ephemeral-networks")
+            "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):
@@ -197,13 +182,14 @@
         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)
+        key = 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.log.info("Released network request %s", self.wpa_psk_2g)
+        self.dut.droid.connectivityUnregisterNetworkCallback(key)
+        # Ensure we disconnected from the network.
+        time.sleep(10)
         self.dut.ed.clear_all_events()
         # Complete flow for the second request.
         wutils.wifi_connect_using_network_request(self.dut, self.open_5g,
@@ -225,8 +211,8 @@
         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)
+        key = self.dut.droid.connectivityRequestWifiNetwork(self.open_2g, 0)
+        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)
@@ -249,19 +235,25 @@
         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)
+        key1 = 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)
+        key2 = self.dut.droid.connectivityRequestWifiNetwork(self.open_5g, 0)
         self.dut.log.info("Sent network request with %s", self.open_5g)
+        self.dut.ed.clear_all_events()
         # 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)
+        autils.fail_on_event_with_keys(
+            self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+            60,
+            (cconsts.NETWORK_CB_KEY_ID, key1),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST))
 
         # Now complete the flow and ensure we connected to second request.
         wutils.wait_for_wifi_connect_after_network_request(self.dut,
-                                                           self.open_5g)
+                                                           self.open_5g,
+                                                           key2)
 
     @test_tracker_info(uuid="f0bb2213-b3d1-4fb8-bbdc-ad55c4fb05ed")
     def test_connect_to_wpa_psk_2g_which_is_already_approved(self):
@@ -282,13 +274,14 @@
            same network.
         """
         # Complete flow for the first request.
-        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
-                                                  self.wpa_psk_2g)
+        key1 = 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)
+        self.dut.log.info("Released network request %s", self.wpa_psk_2g)
+        self.dut.droid.connectivityUnregisterNetworkCallback(key1)
         # Ensure we disconnected from the network.
-        wutils.wait_for_disconnect(self.dut)
-        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
+        time.sleep(10)
         self.dut.ed.clear_all_events()
 
         # Find bssid for the WPA-PSK 2G network.
@@ -302,13 +295,17 @@
         # 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)
+        key2 = self.dut.droid.connectivityRequestWifiNetwork(
+            network_specifier_with_bssid, 0)
         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])
+        autils.wait_for_event_with_keys(
+            self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+            60,
+            (cconsts.NETWORK_CB_KEY_ID, key2),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_AVAILABLE))
 
     @test_tracker_info(uuid="fcf84d94-5f6e-4bd6-9f76-40a0228d4ebe")
     def test_connect_to_wpa_psk_2g_which_is_already_approved_but_then_forgot(self):
@@ -333,18 +330,31 @@
         10.Ensure that the device bypasses user approval now & connects to the
            same network.
         """
+        # If the device supports STA + STA, user cannot trigger disconnect from UI.
+        # Skip the test in that case since the user disconnect causes a disconnect
+        # in the primary STA which will cause the test to fail.
+        if self.sta_concurrency_supported_models:
+            asserts.skip(
+                ("Device %s supports STA + STA, skipping test.")
+                % self.dut.model)
         # Complete flow for the first request.
-        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
-                                                  self.wpa_psk_2g)
+        key1 = wutils.wifi_connect_using_network_request(self.dut,
+                                                         self.wpa_psk_2g,
+                                                         self.wpa_psk_2g)
 
         # Simulate user forgeting the ephemeral network.
+        self.dut.log.info("Triggered user disconnect from %s", self.wpa_psk_2g)
         self.dut.droid.wifiUserDisconnectNetwork(self.wpa_psk_2g[WifiEnums.SSID_KEY])
         # Ensure we disconnected from the network.
-        wutils.wait_for_disconnect(self.dut)
+        autils.wait_for_event_with_keys(
+            self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+            60,
+            (cconsts.NETWORK_CB_KEY_ID, key1),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST))
         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)
+        self.dut.droid.connectivityUnregisterNetworkCallback(key1)
 
         # Find bssid for the WPA-PSK 2G network.
         scan_results = self.dut.droid.wifiGetScanResults()
@@ -357,30 +367,34 @@
         # 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)
+        key2 = self.dut.droid.connectivityRequestWifiNetwork(
+            network_specifier_with_bssid, 0)
         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)
+        autils.fail_on_event_with_keys(
+            self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+            60,
+            (cconsts.NETWORK_CB_KEY_ID, key2),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_AVAILABLE))
 
         # 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)
+                                                           self.wpa_psk_2g,
+                                                           key2)
 
         # Now make the same request again & ensure that we connect without user
         # approval.
-        self.dut.droid.wifiRequestNetworkWithSpecifier(
-            network_specifier_with_bssid)
+        key3 = self.dut.droid.connectivityRequestWifiNetwork(
+            network_specifier_with_bssid, 0)
         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])
+        autils.wait_for_event_with_keys(
+            self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+            60,
+            (cconsts.NETWORK_CB_KEY_ID, key3),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_AVAILABLE))
 
     @test_tracker_info(uuid="2f90a266-f04d-4932-bb5b-d075bedfd56d")
     def test_match_failure_with_invalid_ssid_pattern(self):
@@ -406,10 +420,7 @@
         network_specifier[WifiEnums.SSID_PATTERN_KEY] = \
             network_ssid + "blah" + ".*"
 
-        self.dut.droid.wifiStartTrackingStateChange()
-        expected_ssid = network[WifiEnums.SSID_KEY]
-
-        self.dut.droid.wifiRequestNetworkWithSpecifierWithTimeout(
+        key = self.dut.droid.connectivityRequestWifiNetwork(
               network_specifier, NETWORK_REQUEST_TIMEOUT_MS)
         self.dut.log.info("Sent network request with invalid specifier %s",
                     network_specifier)
@@ -417,14 +428,11 @@
         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()
+        autils.wait_for_event_with_keys(
+            self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+            timeout_secs,
+            (cconsts.NETWORK_CB_KEY_ID, key),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
 
     @test_tracker_info(uuid="caa96f57-840e-4997-9280-655edd3b76ee")
     def test_connect_failure_user_rejected(self):
@@ -445,8 +453,8 @@
 
         self.dut.droid.wifiStartTrackingStateChange()
 
-        self.dut.droid.wifiRequestNetworkWithSpecifierWithTimeout(
-              network, NETWORK_REQUEST_TIMEOUT_MS)
+        key = self.dut.droid.connectivityRequestWifiNetwork(
+            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()
@@ -478,11 +486,11 @@
 
             # 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.")
+            autils.wait_for_event_with_keys(
+                self.dut, cconsts.EVENT_NETWORK_CALLBACK,
+                NETWORK_REQUEST_INSTANT_FAILURE_TIMEOUT_SEC,
+                (cconsts.NETWORK_CB_KEY_ID, key),
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_UNAVAILABLE))
         except queue.Empty:
             asserts.fail("Expected events not returned")
         finally:
diff --git a/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py b/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
index a50b17b..0d13a0b 100644
--- a/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
+++ b/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
@@ -31,10 +31,6 @@
 
 AP_1 = 0
 AP_2 = 1
-AP_1_2G_ATTENUATOR = 0
-AP_1_5G_ATTENUATOR = 1
-AP_2_2G_ATTENUATOR = 2
-AP_2_5G_ATTENUATOR = 3
 # WifiNetworkSelector imposes a 10 seconds gap between two selections
 NETWORK_SELECTION_TIME_GAP = 12
 LVL1_ATTN = 15
@@ -48,40 +44,46 @@
     """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.legacy_configure_ap_and_start(ap_count=2, mirror_ap=False)
+        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()
-        self.pcap_procs = wutils.start_pcap(
-            self.packet_capture, 'dual', self.test_name)
         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 on_pass(self, test_name, begin_time):
-        wutils.stop_pcap(self.packet_capture, self.pcap_procs, True)
-
-    def on_fail(self, test_name, begin_time):
-        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.cat_adb_log(test_name, begin_time)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
@@ -125,7 +127,8 @@
         self.log.info("Actual network: %s", actual_network)
         asserts.assert_true(
             actual_network and WifiEnums.BSSID_KEY in actual_network and \
-                expected_bssid == actual_network[WifiEnums.BSSID_KEY],
+                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)
@@ -144,12 +147,14 @@
         self.add_networks(self.dut, networks)
 
         # move the DUT in range
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
         time.sleep(ATTN_SLEEP)
 
         # verify DUT is connected to AP_1 5g network
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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="3ea818f2-10d7-4aad-bfab-7d8fb25aae78")
     def test_network_selector_basic_connection_prefer_5g(self):
@@ -164,13 +169,15 @@
         self.add_networks(self.dut, networks)
 
         # Move DUT in range
-        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(MIN_ATTN)
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        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
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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="bebb29ca-4486-4cde-b390-c5f8f2e1580c")
     def test_network_selector_prefer_stronger_rssi(self):
@@ -186,13 +193,15 @@
         self.add_networks(self.dut, networks)
 
         # move the DUT in range
-        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(LVL1_ATTN)
-        self.attenuators[AP_2_2G_ATTENUATOR].set_atten(LVL2_ATTN)
+        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
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[AP_1]['2g'])
+        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):
@@ -208,12 +217,14 @@
         self.add_networks(self.dut, networks)
 
         # Move DUT in range
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        self.attenuators[self.ap1_5g_attn].set_atten(MIN_ATTN)
         time.sleep(ATTN_SLEEP)
 
         # verify DUT connects to secure network
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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="ab2c527c-0f9c-4f09-a13f-e3f461b7da52")
     def test_network_selector_blacklist_by_connection_failure(self):
@@ -231,8 +242,8 @@
         self.add_networks(self.dut, networks)
 
         # make AP_1 5G has stronger RSSI than AP_2 5G
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        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
@@ -242,8 +253,10 @@
             time.sleep(NETWORK_SELECTION_TIME_GAP)
 
         # verify DUT is connect AP_2 5G
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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):
@@ -351,23 +364,24 @@
 
         # make both AP_1 5G and AP_2 5G in range, and AP_1 5G
         # has stronger RSSI than AP_2 5G
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(LVL1_ATTN)
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(LVL2_ATTN)
+        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
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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)
 
         # bump up AP_2 5G RSSI over AP_1 5G RSSI
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        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(
-            self.reference_networks[AP_1]['5g'])
+        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):
@@ -378,8 +392,8 @@
             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[AP_2_5G_ATTENUATOR].set_atten(MIN_ATTN)
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        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
@@ -392,8 +406,10 @@
         time.sleep(NETWORK_SELECTION_TIME_GAP)
 
         # verify we are still connected to AP_1 5G
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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):
@@ -411,18 +427,22 @@
 
         # make both AP_1 5G and AP_2 5G in range. AP_1 5G has stronger
         # RSSI than AP_2 5G
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        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
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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)
 
         # forget AP_1
         wutils.wifi_forget_network(
             self.dut, self.reference_networks[AP_1]['5g']['SSID'])
 
         # verify DUT connected to AP2
-        self.connect_and_verify_connected_bssid(
-            self.reference_networks[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)
diff --git a/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py b/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
index 764b40c..f782760 100644
--- a/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
+++ b/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
@@ -36,6 +36,7 @@
 EapPhase2 = WifiEnums.EapPhase2
 # Enterprise Config Macros
 Ent = WifiEnums.Enterprise
+BOINGO = 1
 ATT = 2
 # Suggestion network Macros
 Untrusted = "untrusted"
@@ -59,16 +60,18 @@
     * 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"]
+            "domain_suffix_match", "wifi6_models"]
         self.unpack_userparams(opt_param_names=opt_param,)
 
         if "AccessPoint" in self.user_params:
@@ -76,7 +79,9 @@
                 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"]
@@ -84,35 +89,19 @@
         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, "ent_networks") and isinstance(self.ent_networks,list):
-            self.ent_network_2g = self.ent_networks[0]["2g"]
-            self.ent_network_5g = self.ent_networks[0]["5g"]
-            self.config_aka = {
-                Ent.EAP: int(EAP.AKA),
-                WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
-                "carrierId": str(self.dut.droid.telephonyGetSimCarrierId()),
-            }
-            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,
-            }
         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 = self.passpoint_networks[BOINGO]
             self.passpoint_network[WifiEnums.SSID_KEY] = \
-                self.passpoint_networks[ATT][WifiEnums.SSID_KEY][0]
+                self.passpoint_networks[BOINGO][WifiEnums.SSID_KEY][0]
         self.dut.droid.wifiRemoveNetworkSuggestions([])
         self.dut.adb.shell(
-            "pm disable com.google.android.apps.carrier.carrierwifi")
+            "pm disable com.google.android.apps.carrier.carrierwifi", ignore_status=True)
 
     def setup_test(self):
+        super().setup_test()
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
         self.dut.unlock_screen()
@@ -120,8 +109,18 @@
         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([])
@@ -131,10 +130,6 @@
         self.dut.ed.clear_all_events()
         self.clear_carrier_approved(str(self.dut.droid.telephonyGetSimCarrierId()))
 
-    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)
-
     def teardown_class(self):
         self.dut.adb.shell(
             "pm enable com.google.android.apps.carrier.carrierwifi")
@@ -245,12 +240,16 @@
 
         self.add_suggestions_and_ensure_connection(
             network_suggestions, wifi_network[WifiEnums.SSID_KEY], None)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
         # 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])
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
         self.remove_suggestions_disconnect_and_ensure_no_connection_back(
             network_suggestions, wifi_network[WifiEnums.SSID_KEY])
@@ -478,6 +477,11 @@
         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(
@@ -500,6 +504,15 @@
         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.DOM_SUFFIX_MATCH: self.domain_suffix_match,
+        }
         config = dict(self.config_ttls)
         config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
 
@@ -615,7 +628,6 @@
             [network_suggestion], network_suggestion[WifiEnums.SSID_KEY])
 
     @test_tracker_info(uuid="806dff14-7543-482b-bd0a-598de59374b3")
-    @WifiBaseTest.wifi_test_wrap
     def test_connect_to_passpoint_network_with_post_connection_broadcast(self):
         """ Adds a passpoint network suggestion and ensure that the device connected.
 
@@ -640,7 +652,6 @@
             self.clear_carrier_approved(passpoint_config["carrierId"])
 
     @test_tracker_info(uuid="159b8b8c-fb00-4d4e-a29f-606881dcbf44")
-    @WifiBaseTest.wifi_test_wrap
     def test_connect_to_passpoint_network_reboot_config_store(self):
         """
         Adds a passpoint network suggestion and ensure that the device connects to it
@@ -667,7 +678,6 @@
             self.clear_carrier_approved(passpoint_config["carrierId"])
 
     @test_tracker_info(uuid="34f3d28a-bedf-43fe-a12d-2cfadf6bc6eb")
-    @WifiBaseTest.wifi_test_wrap
     def test_fail_to_connect_to_passpoint_network_when_not_approved(self):
         """
         Adds a passpoint network suggestion and ensure that the device does not
@@ -726,7 +736,6 @@
             self.clear_carrier_approved(passpoint_config["carrierId"])
 
     @test_tracker_info(uuid="cf624cda-4d25-42f1-80eb-6c717fb08338")
-    @WifiBaseTest.wifi_test_wrap
     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.
@@ -742,7 +751,9 @@
         """
         asserts.skip_if(not hasattr(self, "passpoint_networks"),
                         "No passpoint networks, skip this test")
-        passpoint_config = self.passpoint_network
+        passpoint_config = self.passpoint_networks[ATT]
+        passpoint_config[WifiEnums.SSID_KEY] = self.passpoint_networks[
+                ATT][WifiEnums.SSID_KEY][0]
         asserts.skip_if("carrierId" not in passpoint_config,
                         "Not a SIM based passpoint network, skip this test")
 
diff --git a/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py b/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
index 6be4e5c..77b1e64 100644
--- a/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
+++ b/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
@@ -29,6 +29,11 @@
 
 
 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.
 
@@ -53,7 +58,7 @@
 
         self.dut = self.android_devices[0]
         wutils.wifi_test_device_init(self.dut)
-        req_params = ("atten_val", "ping_addr", "max_bugreports")
+        req_params = ("atten_val", "ping_addr")
         opt_param = ["reference_networks"]
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
@@ -82,11 +87,6 @@
             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.
@@ -116,7 +116,7 @@
 
         Args:
             attn_value: Attenuation value for different APs signal.
-            bssid: Bssid of excepted network.
+            bssid: Bssid of expected network.
 
         Returns:
             True if bssid of current network match, else false.
@@ -125,6 +125,7 @@
         self.attenuators[1].set_atten(attn_value[1])
         self.attenuators[2].set_atten(attn_value[2])
         self.attenuators[3].set_atten(attn_value[3])
+        time.sleep(10) # wait time for attenuation
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
         try:
@@ -140,12 +141,6 @@
             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)
-
     def teardown_class(self):
         for ad in self.android_devices:
             wutils.reset_wifi(ad)
@@ -153,6 +148,15 @@
             del self.user_params["reference_networks"]
             del self.user_params["open_network"]
 
+    def setup_test(self):
+        super().setup_test()
+        # initialize attenuators
+        self.attenuators[0].set_atten(0)
+        self.attenuators[1].set_atten(0)
+        self.attenuators[2].set_atten(90)
+        self.attenuators[3].set_atten(90)
+
+
     """ Tests Begin """
 
     """ Test wifi auto join functionality move in range of AP1.
diff --git a/acts_tests/tests/google/wifi/WifiNewSetupWifiToWifiAutoJoinTest.py b/acts_tests/tests/google/wifi/WifiNewSetupWifiToWifiAutoJoinTest.py
new file mode 100644
index 0000000..4f21f0f
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiNewSetupWifiToWifiAutoJoinTest.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2021 - 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.test_decorators import test_tracker_info
+from WifiNewSetupAutoJoinTest import WifiNewSetupAutoJoinTest
+
+class WifiNewSetupWifiToWifiAutoJoinTest(WifiNewSetupAutoJoinTest):
+    """Test Wifi to Wifi auto-switching.
+
+    Note that tests are inherited from WifiNewSetupAutoJoinTest. The only
+    modification is in setup_test, where we ensure Wifi is connected before
+    switching networks
+    """
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        # Since this test class is inherited, the test cases from the inherited
+        # class are also run. Adding the self.tests that are specific to only
+        # this class.
+        self.tests = (
+            "test_wifi_to_wifi_autojoin_Ap1_2g_AP1_20_AP2_95_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_2g_AP1_15_AP2_95_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_2g_AP1_10_AP2_95_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_2g_AP1_5_AP2_95_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_2gto5g_AP1_55_AP2_10_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_2gto5g_AP1_50_AP2_10_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_2gto5g_AP1_45_AP2_10_AP3_95",
+            "test_wifi_to_wifi_autojoin_in_AP1_5gto2g_AP1_5_AP2_80_AP3_95",
+            "test_wifi_to_wifi_autojoin_in_AP1_5gto2g_AP1_10_AP2_75_AP3_95",
+            "test_wifi_to_wifi_autojoin_in_AP1_5gto2g_AP1_15_AP2_70_AP3_95",
+            "test_wifi_to_wifi_autojoin_switch_AP1toAp2_AP1_65_AP2_75_AP3_2",
+            "test_wifi_to_wifi_autojoin_switch_AP1toAp2_AP1_70_AP2_70_AP3_2",
+            "test_wifi_to_wifi_autojoin_switch_AP1toAp2_AP1_75_AP2_65_AP3_2",
+            "test_wifi_to_wifi_autojoin_Ap2_2gto5g_AP1_70_AP2_85_AP3_75",
+            "test_wifi_to_wifi_autojoin_Ap2_2gto5g_AP1_75_AP2_80_AP3_75",
+            "test_wifi_to_wifi_autojoin_Ap2_2gto5g_AP1_75_AP2_75_AP3_75",
+            "test_wifi_to_wifi_autojoin_Ap2_5gto2g_AP1_75_AP2_70_AP3_10",
+            "test_wifi_to_wifi_autojoin_Ap2_5gto2g_AP1_75_AP2_75_AP3_10",
+            "test_wifi_to_wifi_autojoin_Ap2_5gto2g_AP1_75_AP2_80_AP3_10",
+            "test_wifi_to_wifi_autojoin_out_of_range",
+            "test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_85_AP3_10",
+            "test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_80_AP3_10",
+            "test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_75_AP3_10",
+            "test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_70_AP3_10",
+            "test_wifi_to_wifi_autojoin_in_Ap2_5gto2g_AP1_75_AP2_70_AP3_10",
+            "test_wifi_to_wifi_autojoin_in_Ap2_5gto2g_AP1_75_AP2_75_AP3_10",
+            "test_wifi_to_wifi_autojoin_in_Ap2_5gto2g_AP1_75_AP2_80_AP3_10",
+            "test_wifi_to_wifi_autojoin_switch_AP2toAp1_AP1_15_AP2_65_AP3_75",
+            "test_wifi_to_wifi_autojoin_switch_AP2toAp1_AP1_10_AP2_70_AP3_75",
+            "test_wifi_to_wifi_autojoin_switch_AP2toAp1_AP1_5_AP2_75_AP3_75",
+            "test_wifi_to_wifi_autojoin_Ap1_5gto2g_AP1_10_AP2_80_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_5gto2g_AP1_15_AP2_80_AP3_95",
+            "test_wifi_to_wifi_autojoin_Ap1_5gto2g_AP1_20_AP2_80_AP3_95",
+        )
+
+    def setup_test(self):
+        super().setup_test()
+        # Attenuate all other networks except network 0's 2.4 GHz STA, and
+        # validate we connect to it.
+        # This ensures that we are connected to something at the beginning of
+        # each test, in order to test Wifi-to-Wifi switching.
+        self.set_attn_and_validate_connection(
+            (0, 90, 90, 90),
+            self.reference_networks[0]["2g"]['bssid'])
+
+    """ Tests Begin """
+
+    @test_tracker_info(uuid="205dbf01-cb2a-41b7-8945-4b1d0c4fb443")
+    def test_wifi_to_wifi_autojoin_Ap1_2g_AP1_20_AP2_95_AP3_95(self):
+        super().test_autojoin_Ap1_2g_AP1_20_AP2_95_AP3_95()
+
+    @test_tracker_info(uuid="90d944a0-70fc-4ab0-a786-0ac8e967dbf6")
+    def test_wifi_to_wifi_autojoin_Ap1_2g_AP1_15_AP2_95_AP3_95(self):
+        super().test_autojoin_Ap1_2g_AP1_15_AP2_95_AP3_95()
+
+    @test_tracker_info(uuid="cab7d874-83e0-444a-b538-b6f959a25091")
+    def test_wifi_to_wifi_autojoin_Ap1_2g_AP1_10_AP2_95_AP3_95(self):
+        super().test_autojoin_Ap1_2g_AP1_10_AP2_95_AP3_95()
+
+    @test_tracker_info(uuid="b7914791-e801-49b7-a533-7b3a992253c6")
+    def test_wifi_to_wifi_autojoin_Ap1_2g_AP1_5_AP2_95_AP3_95(self):
+        super().test_autojoin_Ap1_2g_AP1_5_AP2_95_AP3_95()
+
+    @test_tracker_info(uuid="82af1667-0859-4074-9006-70b0a7896f1d")
+    def test_wifi_to_wifi_autojoin_Ap1_2gto5g_AP1_55_AP2_10_AP3_95(self):
+        super().test_autojoin_Ap1_2gto5g_AP1_55_AP2_10_AP3_95()
+
+    @test_tracker_info(uuid="a8dae171-f98f-46ea-837e-86614719f1ea")
+    def test_wifi_to_wifi_autojoin_Ap1_2gto5g_AP1_50_AP2_10_AP3_95(self):
+        super().test_autojoin_Ap1_2gto5g_AP1_50_AP2_10_AP3_95()
+
+    @test_tracker_info(uuid="f70d6d59-430f-45cf-928f-bbc0609fe07e")
+    def test_wifi_to_wifi_autojoin_Ap1_2gto5g_AP1_45_AP2_10_AP3_95(self):
+        super().test_autojoin_Ap1_2gto5g_AP1_45_AP2_10_AP3_95()
+
+    @test_tracker_info(uuid="16cedd3a-84e4-4b85-87c5-7658b8b8e5fb")
+    def test_wifi_to_wifi_autojoin_in_AP1_5gto2g_AP1_5_AP2_80_AP3_95(self):
+        super().test_autojoin_in_AP1_5gto2g_AP1_5_AP2_80_AP3_95()
+
+    @test_tracker_info(uuid="5a2461c8-fa2b-418b-a6d2-9ed980a1932e")
+    def test_wifi_to_wifi_autojoin_in_AP1_5gto2g_AP1_10_AP2_75_AP3_95(self):
+        super().test_autojoin_in_AP1_5gto2g_AP1_10_AP2_75_AP3_95()
+
+    @test_tracker_info(uuid="340d7d02-30e6-4ed9-a28c-0b2a1050c91c")
+    def test_wifi_to_wifi_autojoin_in_AP1_5gto2g_AP1_15_AP2_70_AP3_95(self):
+        super().test_autojoin_in_AP1_5gto2g_AP1_15_AP2_70_AP3_95()
+
+    @test_tracker_info(uuid="dadf079f-6634-47e7-852a-4aaad3905f57")
+    def test_wifi_to_wifi_autojoin_switch_AP1toAp2_AP1_65_AP2_75_AP3_2(self):
+        super().test_autojoin_swtich_AP1toAp2_AP1_65_AP2_75_AP3_2()
+
+    @test_tracker_info(uuid="29a842a0-427a-41f7-8243-6cce34fac2f7")
+    def test_wifi_to_wifi_autojoin_switch_AP1toAp2_AP1_70_AP2_70_AP3_2(self):
+        super().test_autojoin_swtich_AP1toAp2_AP1_70_AP2_70_AP3_2()
+
+    @test_tracker_info(uuid="881c98cc-ddda-43b6-91af-a50152ae47bb")
+    def test_wifi_to_wifi_autojoin_switch_AP1toAp2_AP1_75_AP2_65_AP3_2(self):
+        super().test_autojoin_swtich_AP1toAp2_AP1_75_AP2_65_AP3_2()
+
+    @test_tracker_info(uuid="a81c2a3b-3fe5-4b8f-9282-4d09718d0e40")
+    def test_wifi_to_wifi_autojoin_Ap2_2gto5g_AP1_70_AP2_85_AP3_75(self):
+        super().test_autojoin_Ap2_2gto5g_AP1_70_AP2_85_AP3_75()
+
+    @test_tracker_info(uuid="ac7605cd-1bcd-488f-ba53-70d9d6a6231a")
+    def test_wifi_to_wifi_autojoin_Ap2_2gto5g_AP1_75_AP2_80_AP3_75(self):
+        super().test_autojoin_Ap2_2gto5g_AP1_75_AP2_80_AP3_75()
+
+    @test_tracker_info(uuid="7757cae4-9cd4-48ba-a6d9-67e70093a76d")
+    def test_wifi_to_wifi_autojoin_Ap2_2gto5g_AP1_75_AP2_75_AP3_75(self):
+        super().test_autojoin_Ap2_2gto5g_AP1_75_AP2_75_AP3_75()
+
+    @test_tracker_info(uuid="51ed8923-9409-403f-8001-4da130903d4e")
+    def test_wifi_to_wifi_autojoin_Ap2_5gto2g_AP1_75_AP2_70_AP3_10(self):
+        super().test_autojoin_Ap2_5gto2g_AP1_75_AP2_70_AP3_10()
+
+    @test_tracker_info(uuid="f818b6e0-a1af-4a8c-bffd-828752d5f5ba")
+    def test_wifi_to_wifi_autojoin_Ap2_5gto2g_AP1_75_AP2_75_AP3_10(self):
+        super().test_autojoin_Ap2_5gto2g_AP1_75_AP2_75_AP3_10()
+
+    @test_tracker_info(uuid="70958be1-bb6b-4cb5-9904-7acb8635ad93")
+    def test_wifi_to_wifi_autojoin_Ap2_5gto2g_AP1_75_AP2_80_AP3_10(self):
+        super().test_autojoin_Ap2_5gto2g_AP1_75_AP2_80_AP3_10()
+
+    @test_tracker_info(uuid="f9e97a28-3dae-485b-af2a-acb8ac170607")
+    def test_wifi_to_wifi_autojoin_out_of_range(self):
+        super().test_autojoin_out_of_range()
+
+    @test_tracker_info(uuid="c0f93393-9311-41ea-a5da-781af5101515")
+    def test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_85_AP3_10(self):
+        super().test_autojoin_Ap2_2g_AP1_75_AP2_85_AP3_10()
+
+    @test_tracker_info(uuid="04f23c21-3f3e-4f84-89a1-cd88c616ee7d")
+    def test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_80_AP3_10(self):
+        super().test_autojoin_Ap2_2g_AP1_75_AP2_80_AP3_10()
+
+    @test_tracker_info(uuid="9cb970cf-e8f7-4579-9cd4-d1de40aeb231")
+    def test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_75_AP3_10(self):
+        super().test_autojoin_Ap2_2g_AP1_75_AP2_75_AP3_10()
+
+    @test_tracker_info(uuid="b1c73364-ff1e-450a-9818-d24958a9d9de")
+    def test_wifi_to_wifi_autojoin_Ap2_2g_AP1_75_AP2_70_AP3_10(self):
+        super().test_autojoin_Ap2_2g_AP1_75_AP2_70_AP3_10()
+
+    @test_tracker_info(uuid="76994fa0-1239-4289-9bce-1ec6841abd0d")
+    def test_wifi_to_wifi_autojoin_in_Ap2_5gto2g_AP1_75_AP2_70_AP3_10(self):
+        super().test_autojoin_in_Ap2_5gto2g_AP1_75_AP2_70_AP3_10()
+
+    @test_tracker_info(uuid="5f459757-9b55-4e49-8549-ed25a8fbbe71")
+    def test_wifi_to_wifi_autojoin_in_Ap2_5gto2g_AP1_75_AP2_75_AP3_10(self):
+        super().test_autojoin_in_Ap2_5gto2g_AP1_75_AP2_75_AP3_10()
+
+    @test_tracker_info(uuid="82f2b16c-622d-44cd-b84c-7c6da63a9b0a")
+    def test_wifi_to_wifi_autojoin_in_Ap2_5gto2g_AP1_75_AP2_80_AP3_10(self):
+        super().test_autojoin_in_Ap2_5gto2g_AP1_75_AP2_80_AP3_10()
+
+    @test_tracker_info(uuid="a6b31b17-5dba-4709-b603-62f324e4da0c")
+    def test_wifi_to_wifi_autojoin_switch_AP2toAp1_AP1_15_AP2_65_AP3_75(self):
+        super().test_autojoin_swtich_AP2toAp1_AP1_15_AP2_65_AP3_75()
+
+    @test_tracker_info(uuid="e69ad2fa-817b-4f57-854b-a2e0cf1ddf20")
+    def test_wifi_to_wifi_autojoin_switch_AP2toAp1_AP1_10_AP2_70_AP3_75(self):
+        super().test_autojoin_swtich_AP2toAp1_AP1_10_AP2_70_AP3_75()
+
+    @test_tracker_info(uuid="c3983109-a8ed-4161-aa8b-dfefe0068991")
+    def test_wifi_to_wifi_autojoin_switch_AP2toAp1_AP1_5_AP2_75_AP3_75(self):
+        super().test_autojoin_swtich_AP2toAp1_AP1_5_AP2_75_AP3_75()
+
+    @test_tracker_info(uuid="6c5ac0b7-a027-44cd-8b03-3a05952bd6f7")
+    def test_wifi_to_wifi_autojoin_Ap1_5gto2g_AP1_10_AP2_80_AP3_95(self):
+        super().test_autojoin_Ap1_5gto2g_AP1_10_AP2_80_AP3_95()
+
+    @test_tracker_info(uuid="87e52e7c-ab86-41a9-b301-987c526141a4")
+    def test_wifi_to_wifi_autojoin_Ap1_5gto2g_AP1_15_AP2_80_AP3_95(self):
+        super().test_autojoin_Ap1_5gto2g_AP1_15_AP2_80_AP3_95()
+
+    @test_tracker_info(uuid="f48f45ad-3bde-4b19-8c0f-5abf5ece5acd")
+    def test_wifi_to_wifi_autojoin_Ap1_5gto2g_AP1_20_AP2_80_AP3_95(self):
+        super().test_autojoin_Ap1_5gto2g_AP1_20_AP2_80_AP3_95()
+
+    """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiPasspointTest.py b/acts_tests/tests/google/wifi/WifiPasspointTest.py
index 950506e..4949195 100755
--- a/acts_tests/tests/google/wifi/WifiPasspointTest.py
+++ b/acts_tests/tests/google/wifi/WifiPasspointTest.py
@@ -23,19 +23,16 @@
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
-
+import WifiManagerTest
 from acts import asserts
 from acts import signals
-from acts.libs.uicd.uicd_cli import UicdCli
-from acts.libs.uicd.uicd_cli import UicdError
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
-from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import force_airplane_mode
 
 WifiEnums = wutils.WifiEnums
 
-DEFAULT_TIMEOUT = 10
+DEFAULT_TIMEOUT = 15
 OSU_TEST_TIMEOUT = 300
 
 # Constants for providers.
@@ -66,30 +63,30 @@
     """
 
     def setup_class(self):
+        super().setup_class()
         self.dut = self.android_devices[0]
         wutils.wifi_test_device_init(self.dut)
-        req_params = ["passpoint_networks", "uicd_workflows", "uicd_zip"]
-        opt_param = []
-        self.unpack_userparams(
-            req_param_names=req_params, opt_param_names=opt_param)
-        self.unpack_userparams(req_params)
+        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
-        # Setup Uicd cli object for UI interation.
-        self.ui = UicdCli(self.uicd_zip[0], self.uicd_workflows)
-        self.passpoint_workflow = "passpoint-login_%s" % self.dut.model
 
 
     def setup_test(self):
+        super().setup_test()
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
         self.dut.unlock_screen()
+        self.dut.adb.shell("input keyevent KEYCODE_HOME")
 
 
     def teardown_test(self):
+        super().teardown_test()
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
         passpoint_configs = self.dut.droid.getPasspointConfigs()
@@ -97,6 +94,7 @@
             wutils.delete_passpoint(self.dut, config)
         wutils.reset_wifi(self.dut)
 
+
     """Helper Functions"""
 
 
@@ -148,6 +146,39 @@
             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 & password
+        screen_dump = uutils.get_screen_dump_xml(self.dut)
+        nodes = screen_dump.getElementsByTagName("node")
+        index = 0
+        for node in nodes:
+            if uutils.match_node(node, class_name="android.widget.EditText"):
+                x, y = eval(node.attributes["bounds"].value.split("][")[0][1:])
+                self.dut.adb.shell("input tap %s %s" % (x, y))
+                if index == 0:
+                    self.dut.adb.shell("input text %s" % self.boingo_username)
+                    index += 1
+                else:
+                    self.dut.adb.shell("input text %s" % self.boingo_password)
+                    break
+                self.dut.adb.shell("input keyevent 111")
+        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)
+        time.sleep(DEFAULT_TIMEOUT)
+
     def start_subscription_provisioning(self, state):
         """Start subscription provisioning with a default provider."""
 
@@ -185,7 +216,8 @@
                     "Passpoint Provisioning status %s" % dut_event['data'][
                         'status'])
                 if int(dut_event['data']['status']) == 7:
-                    self.ui.run(self.dut.serial, self.passpoint_workflow)
+                    time.sleep(DEFAULT_TIMEOUT)
+                    self.ui_automator_boingo()
         # Clear all previous events.
         self.dut.ed.clear_all_events()
 
@@ -199,7 +231,7 @@
                                           "expected_ssids"])
         # Delete the Passpoint profile.
         self.get_configured_passpoint_and_delete()
-        wutils.wait_for_disconnect(self.dut)
+        wutils.wait_for_disconnect(self.dut, timeout=15)
 
 
     """Tests"""
@@ -293,6 +325,22 @@
             raise signals.TestFailure("Failed because an unknown FQDN"
                                       " was successfully deleted.")
 
+    @test_tracker_info(uuid="ac71344f-e5d9-4e70-b15d-8ce24a4b3744")
+    def test_global_reach_passpoint(self):
+        """Test connection to global reach passpoint.
+
+        Steps:
+          1. Install global reach passpoint profile.
+          2. Verify connection to the global reach wifi network.
+          3. Delete passpoint and verify device disconnects.
+        """
+        passpoint_config = self.passpoint_networks[GLOBAL_RE]
+        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="bf03c03a-e649-4e2b-a557-1f791bd98951")
     def test_passpoint_failover(self):
@@ -362,7 +410,6 @@
 
 
     @test_tracker_info(uuid="e3e826d2-7c39-4c37-ab3f-81992d5aa0e8")
-    @WifiBaseTest.wifi_test_wrap
     def test_att_passpoint_network(self):
         """Add a AT&T Passpoint network and verify device connects to it.
 
@@ -376,8 +423,8 @@
         """
         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)
+        if operator not in carriers:
+            self.log.warn("Device %s does not have a ATT sim" % self.dut.model)
 
         passpoint_config = self.passpoint_networks[ATT]
         self.install_passpoint_profile(passpoint_config)
diff --git a/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py b/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
index 27703d2..0eed450 100644
--- a/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
+++ b/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
@@ -18,6 +18,7 @@
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
 from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 class WifiPerformancePreflightTest(base_test.BaseTestClass):
@@ -43,25 +44,24 @@
         # Load BDF and firmware if needed
         if hasattr(self, 'bdf'):
             self.log.info('Pushing WiFi BDF to DUT.')
-            wputils.push_bdf(self.dut, self.bdf[0])
+            wputils.push_config(self.dut, self.bdf[0])
         if hasattr(self, 'firmware'):
             self.log.info('Pushing WiFi firmware to DUT.')
-            wlanmdsp = [
-                file for file in self.firmware if "wlanmdsp.mbn" in file
-            ][0]
-            data_msc = [file for file in self.firmware
-                        if "Data.msc" in file][0]
-            wputils.push_firmware(self.dut, wlanmdsp, data_msc)
+            wputils.push_firmware(self.dut, self.firmware)
 
         for ad in self.android_devices:
+            wutils.wifi_toggle_state(ad, True)
             ad.droid.wifiEnableVerboseLogging(1)
-            ad.adb.shell("wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
-                         "wlan0 log_level EXCESSIVE")
+            try:
+                ad.adb.shell("wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
+                             "wlan0 log_level EXCESSIVE")
+            except:
+                self.log.warning('Could not set log level.')
 
     def test_wifi_sw_signature(self):
         sw_signature = wputils.get_sw_signature(self.dut)
-        self.testcase_metric_logger.add_metric('bdf_signature',
-                                               sw_signature['bdf_signature'])
+        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',
@@ -69,4 +69,4 @@
 
     def teardown_class(self):
         # Teardown AP and release its lockfile
-        self.access_point.teardown()
\ No newline at end of file
+        self.access_point.teardown()
diff --git a/acts_tests/tests/google/wifi/WifiPingTest.py b/acts_tests/tests/google/wifi/WifiPingTest.py
index 83f751b..ea45381 100644
--- a/acts_tests/tests/google/wifi/WifiPingTest.py
+++ b/acts_tests/tests/google/wifi/WifiPingTest.py
@@ -91,7 +91,7 @@
         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.")
+                                'Can not turn on airplane mode.')
         wutils.wifi_toggle_state(self.dut, True)
 
         # Configure test retries
@@ -188,20 +188,18 @@
         Args:
             result: dict containing ping results and meta data
         """
-        # Get target range
-        #rvr_range = self.get_range_from_rvr()
-        # Set Blackbox metric
-        if self.publish_testcase_metrics:
-            self.testcase_metric_logger.add_metric('ping_range',
-                                                   result['range'])
         # 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))
-        else:
-            asserts.explicit_pass(test_message)
+            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']:
@@ -301,7 +299,7 @@
             self.sniffer.start_capture(
                 testcase_params['test_network'],
                 chan=int(testcase_params['channel']),
-                bw=int(testcase_params['mode'][3:]),
+                bw=testcase_params['bandwidth'],
                 duration=testcase_params['ping_duration'] *
                 len(testcase_params['atten_range']) + self.TEST_TIMEOUT)
         # Run ping and sweep attenuation as needed
@@ -415,7 +413,7 @@
             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.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']][
@@ -517,18 +515,22 @@
 
     def generate_test_cases(self, ap_power, channels, modes, chain_mask,
                             test_types):
+        """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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
+
         for channel, mode, chain, test_type in itertools.product(
                 channels, modes, chain_mask, test_types):
-            if channel not in allowed_configs[mode]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             testcase_name = '{}_ch{}_{}_ch{}'.format(test_type, channel, mode,
                                                      chain)
@@ -536,6 +538,7 @@
                                                       ap_power=ap_power,
                                                       channel=channel,
                                                       mode=mode,
+                                                      bandwidth=bandwidth,
                                                       chain_mask=chain)
             setattr(self, testcase_name,
                     partial(self._test_ping, testcase_params))
@@ -549,7 +552,7 @@
         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'],
+            modes=['bw20', 'bw40', 'bw80'],
             test_types=[
                 'test_ping_range', 'test_fast_ping_rtt', 'test_slow_ping_rtt'
             ],
@@ -563,7 +566,7 @@
             ap_power='standard',
             chain_mask=['0', '1', '2x2'],
             channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            modes=['VHT20', 'VHT40', 'VHT80'],
+            modes=['bw20', 'bw40', 'bw80'],
             test_types=['test_ping_range'])
 
 
@@ -574,7 +577,7 @@
             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'],
+            modes=['bw20', 'bw40', 'bw80'],
             test_types=['test_ping_range'])
 
 
@@ -716,16 +719,18 @@
                             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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
         for channel, mode, position in itertools.product(
                 channels, modes, positions):
-            if channel not in allowed_configs[mode]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             testcase_name = 'test_ping_range_ch{}_{}_pos{}'.format(
                 channel, mode, position)
@@ -734,6 +739,7 @@
                 ap_power=ap_power,
                 channel=channel,
                 mode=mode,
+                bandwidth=bandwidth,
                 chain_mask='2x2',
                 chamber_mode=chamber_mode,
                 total_positions=len(positions),
@@ -749,7 +755,7 @@
         WifiOtaPingTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(ap_power='standard',
                                               channels=[6, 36, 149],
-                                              modes=['VHT20'],
+                                              modes=['bw20'],
                                               chamber_mode='orientation',
                                               positions=list(range(0, 360,
                                                                    10)))
@@ -761,7 +767,7 @@
         self.tests = self.generate_test_cases(
             ap_power='standard',
             channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            modes=['VHT20'],
+            modes=['bw20'],
             chamber_mode='orientation',
             positions=list(range(0, 360, 45)))
 
@@ -771,7 +777,7 @@
         WifiOtaPingTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(ap_power='standard',
                                               channels=[6, 36, 149],
-                                              modes=['VHT20'],
+                                              modes=['bw20'],
                                               chamber_mode='stepped stirrers',
                                               positions=list(range(100)))
 
@@ -781,7 +787,7 @@
         WifiOtaPingTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(ap_power='low_power',
                                               channels=[6, 36, 149],
-                                              modes=['VHT20'],
+                                              modes=['bw20'],
                                               chamber_mode='orientation',
                                               positions=list(range(0, 360,
                                                                    10)))
@@ -793,7 +799,7 @@
         self.tests = self.generate_test_cases(
             ap_power='low_power',
             channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
-            modes=['VHT20'],
+            modes=['bw20'],
             chamber_mode='orientation',
             positions=list(range(0, 360, 45)))
 
@@ -803,6 +809,6 @@
         WifiOtaPingTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(ap_power='low_power',
                                               channels=[6, 36, 149],
-                                              modes=['VHT20'],
+                                              modes=['bw20'],
                                               chamber_mode='stepped stirrers',
-                                              positions=list(range(100)))
+                                              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
index cbbf3e4..d93fa45 100644
--- a/acts_tests/tests/google/wifi/WifiPnoTest.py
+++ b/acts_tests/tests/google/wifi/WifiPnoTest.py
@@ -16,7 +16,6 @@
 import time
 
 from acts import asserts
-from acts import base_test
 from acts.test_decorators import test_tracker_info
 import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
@@ -26,21 +25,29 @@
 
 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"]
+        req_params = ["attn_vals", "pno_interval", "wifi6_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()
-
+        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
@@ -49,6 +56,7 @@
         self.set_attns("default")
 
     def setup_test(self):
+        super().setup_test()
         self.dut.droid.wifiStartTrackingStateChange()
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
@@ -56,15 +64,12 @@
         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 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)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
@@ -112,6 +117,8 @@
             wutils.verify_wifi_connection_info(self.dut, verify_con)
             self.log.info("Connected to %s successfully after PNO",
                           expected_ssid)
+            wutils.verify_11ax_wifi_connection(
+                self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         finally:
             pass
 
@@ -143,7 +150,7 @@
     """ Tests Begin """
 
     @test_tracker_info(uuid="33d3cae4-5fa7-4e90-b9e2-5d3747bba64c")
-    def test_simple_pno_connection_to_2g(self):
+    def test_simple_pno_connection_5g_to_2g(self):
         """Test PNO triggered autoconnect to a network.
 
         Steps:
@@ -157,7 +164,7 @@
         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):
+    def test_simple_pno_connection_2g_to_5g(self):
         """Test PNO triggered autoconnect to a network.
 
         Steps:
@@ -176,20 +183,24 @@
         """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 latest added networks in the list.
-        So add 16 test networks and then add 2 valid networks.
+        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. Run the simple pno test.
+        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("a_on_b_off", self.pno_network_a)
+        self.trigger_pno_and_assert_connect("b_on_a_off", self.pno_network_b)
 
     """ Tests End """
diff --git a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
index e011866..db42e2e 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
@@ -15,6 +15,7 @@
 #   limitations under the License.
 
 import collections
+import copy
 import json
 import math
 import os
@@ -71,9 +72,7 @@
         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))
+        self.access_points = retail_ap.create(self.RetailAccessPoints)
 
         # Get RF connection map
         self.log.info("Getting RF connection map.")
@@ -81,7 +80,7 @@
         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.main_network, True))
         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))
 
@@ -104,7 +103,8 @@
         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 @ {0:.2f}s: {1} -> {2})'.format(
+                event[0], event[1], event[2]))
         self.log.info('Roam transition statistics: {}'.format(
             result['roam_counts']))
 
@@ -295,15 +295,20 @@
         roam_transitions = []
         roam_counts = {}
         total_roams = 0
+        roam_candidates = copy.deepcopy(self.main_network)
+        roam_candidates['disconnected'] = {'BSSID': 'disconnected'}
         for event in roam_events:
             from_bssid = next(
-                key for key, value in self.main_network.items()
+                key for key, value in roam_candidates.items()
                 if value['BSSID'] == result['rssi_result']['bssid'][event[0]])
             to_bssid = next(
-                key for key, value in self.main_network.items()
+                key for key, value in roam_candidates.items()
                 if value['BSSID'] == result['rssi_result']['bssid'][event[1]])
+            if from_bssid == to_bssid:
+                continue
             curr_bssid_transition = (from_bssid, to_bssid)
             curr_roam_transition = (
+                result['rssi_result']['time_stamp'][event[0]],
                 (from_bssid,
                  result['rssi_result']['signal_poll_rssi']['data'][event[0]]),
                 (to_bssid,
@@ -391,7 +396,10 @@
             y_data=result['rssi_result']['signal_poll_rssi']['data'],
             legend='RSSI',
             y_axis='secondary')
-        figure.generate_figure(output_file_path)
+        try:
+            figure.generate_figure(output_file_path)
+        except:
+            pass
 
     def plot_iperf_result(self,
                           testcase_params,
@@ -425,8 +433,10 @@
                         result['rssi_result']['signal_poll_rssi']['data'],
                         'RSSI',
                         y_axis='secondary')
-
-        figure.generate_figure(output_file_path)
+        try:
+            figure.generate_figure(output_file_path)
+        except:
+            pass
 
     def setup_ap(self, testcase_params):
         """Sets up the AP and attenuator to the test configuration.
@@ -467,7 +477,7 @@
         wutils.wifi_connect(self.dut,
                             network,
                             num_of_tries=5,
-                            check_connectivity=False)
+                            check_connectivity=True)
         self.dut.droid.wifiSetEnableAutoJoinWhenAssociated(1)
         self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
         if testcase_params['screen_on']:
@@ -490,10 +500,22 @@
             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)
+        if testcase_params.get('ping_to_dut', False):
+            ping_future = wputils.get_ping_stats_nb(
+                self.remote_server, self.dut_ip,
+                testcase_params['atten_waveforms']['length'],
+                testcase_params['ping_interval'], 64)
+        else:
+            if testcase_params.get('lan_traffic_only', False):
+                ping_address = wputils.get_server_address(
+                    self.remote_server, self.dut_ip, '255.255.255.0')
+            else:
+                ping_address = wputils.get_server_address(
+                    self.remote_server, self.dut_ip, 'public')
+            ping_future = wputils.get_ping_stats_nb(
+                self.dut, ping_address,
+                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'] /
@@ -503,7 +525,7 @@
         return {
             'ping_result': ping_future.result().as_dict(),
             'rssi_result': rssi_future.result(),
-            'ap_settings': self.access_point.ap_settings,
+            'ap_settings': [ap.ap_settings for ap in self.access_points],
         }
 
     def run_iperf_test(self, testcase_params):
@@ -521,8 +543,12 @@
         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')
+            if testcase_params.get('lan_traffic_only', False):
+                iperf_server_address = wputils.get_server_address(
+                    self.remote_server, self.dut_ip, '255.255.255.0')
+            else:
+                iperf_server_address = wputils.get_server_address(
+                    self.remote_server, self.dut_ip, 'public')
         iperf_args = '-i {} -t {} -J'.format(
             IPERF_INTERVAL, testcase_params['atten_waveforms']['length'])
         if not isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
@@ -549,7 +575,7 @@
         return {
             'throughput': instantaneous_rates,
             'rssi_result': rssi_future.result(),
-            'ap_settings': self.access_point.ap_settings,
+            'ap_settings': [ap.ap_settings for ap in self.access_points],
         }
 
     def run_attenuation_waveform(self, testcase_params, step_duration=1):
@@ -581,14 +607,14 @@
             waveform_params: list of dicts representing waveforms to generate
         """
         atten_waveforms = {}
-        for network in list(waveform_params[0]):
+        for network in self.main_network:
             atten_waveforms[network] = []
 
         for waveform in waveform_params:
-            for network, network_waveform in waveform.items():
+            for network_name, network in self.main_network.items():
                 waveform_vector = self.gen_single_atten_waveform(
-                    network_waveform)
-                atten_waveforms[network] += waveform_vector
+                    waveform[network['roaming_label']])
+                atten_waveforms[network_name] += waveform_vector
 
         waveform_lengths = {
             len(atten_waveforms[network])
diff --git a/acts_tests/tests/google/wifi/WifiRoamingTest.py b/acts_tests/tests/google/wifi/WifiRoamingTest.py
index ebbc664..c70e56f 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingTest.py
@@ -13,58 +13,45 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import pprint
-import random
 import time
-from acts import context
-from scapy.all import *
-
 from acts import asserts
-from acts import base_test
+from acts import utils
 from acts import signals
+from acts.keys import Config
 from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.net import connectivity_const as cconsts
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+import os
 
 WifiEnums = wutils.WifiEnums
-DEF_ATTN = 60
-MAX_ATTN = 95
-ROAM_DBM = -75
-WAIT_AFTER_ATTN = 12
-ATTN_STEP = 5
+
 
 class WifiRoamingTest(WifiBaseTest):
 
     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.
-        """
+        """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", "roam_interval", "ping_addr",
-                      "max_bugreports"]
-        opt_param = ["open_network", "reference_networks",]
-        self.unpack_userparams(
-            req_param_names=req_params, opt_param_names=opt_param)
+        self.dut_client = self.android_devices[1]
+        req_params = ["roaming_attn"]
+        self.unpack_userparams(req_param_names=req_params,)
+        self.country_code = wutils.WifiEnums.CountryCode.US
+        if hasattr(self, "country_code_file"):
+            if isinstance(self.country_code_file, list):
+                self.country_code_file = self.country_code_file[0]
+            if not os.path.isfile(self.country_code_file):
+                self.country_code_file = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.country_code_file)
+            self.country_code = utils.load_config(
+                self.country_code_file)["country"]
 
         if "AccessPoint" in self.user_params:
             self.legacy_configure_ap_and_start(ap_count=2)
 
-        asserts.assert_true(
-            len(self.reference_networks) > 1,
-            "Need at least two psk networks for roaming.")
-        asserts.assert_true(
-            len(self.open_network) > 1,
-            "Need at least two open networks for roaming")
-
-
-        self.configure_packet_capture()
-
     def teardown_class(self):
         self.dut.ed.clear_all_events()
         if "AccessPoint" in self.user_params:
@@ -72,20 +59,44 @@
             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)
-        for a in self.attenuators:
-            a.set_atten(0)
+        wutils.set_attns(self.attenuators, "default")
+        for ad in self.android_devices:
+            wutils.set_wifi_country_code(ad, self.country_code)
+        if "OpenWrtAP" in self.user_params:
+            for ap in self.access_points:
+                ap.close()
 
-    def on_fail(self, test_name, begin_time):
-        self.dut.cat_adb_log(test_name, begin_time)
-        self.dut.take_bug_report(test_name, begin_time)
+    ### Helper Methods ###
+
+    def register_network_callback_for_internet(self):
+        self.dut.log.debug("Registering network callback for wifi internet"
+                           "connectivity")
+        network_request = {
+            cconsts.NETWORK_CAP_TRANSPORT_TYPE_KEY :
+                cconsts.NETWORK_CAP_TRANSPORT_WIFI,
+            cconsts.NETWORK_CAP_CAPABILITY_KEY :
+                [cconsts.NETWORK_CAP_CAPABILITY_INTERNET]
+        }
+        key = self.dut.droid.connectivityRegisterNetworkCallback(network_request)
+        return key
+
+    def generate_wifi_info(self, network):
+        return {
+            WifiEnums.SSID_KEY : network[WifiEnums.SSID_KEY],
+            # We need to use "BSSID" in WifiInfo map, also need to use lower
+            # chars for bssid.
+            WifiEnums.BSSID_KEY : network["bssid"].lower()
+        }
 
     def roaming_from_AP1_and_AP2(self, AP1_network, AP2_network):
         """Test roaming between two APs.
@@ -101,143 +112,318 @@
         4. Expect DUT to roam to AP2.
         5. Validate connection information and ping.
         """
-        wutils.set_attns(self.attenuators, "AP1_on_AP2_off")
+        network_cb_key = None
+        if self.dut.droid.isSdkAtLeastS():
+            network_cb_key = self.register_network_callback_for_internet()
+        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)
+        if network_cb_key is not None:
+            self.dut.log.info("Waiting for onAvailable and "
+                              "onCapabilitiesChanged after connection")
+            # Ensure that the connection completed and we got the ON_AVAILABLE
+            # callback.
+            autils.wait_for_event_with_keys(
+                self.dut,
+                cconsts.EVENT_NETWORK_CALLBACK,
+                20,
+                (cconsts.NETWORK_CB_KEY_ID, network_cb_key),
+                (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_AVAILABLE))
+            autils.wait_for_event_with_keys(
+                self.dut, cconsts.EVENT_NETWORK_CALLBACK, 10,
+                (cconsts.NETWORK_CB_KEY_ID, network_cb_key),
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_TRANSPORT_INFO,
+                 self.generate_wifi_info(AP1_network)))
         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.dut, self.attenuators, "AP1_off_AP2_on", AP2_network,
+            self.roaming_attn)
+        if network_cb_key is not None:
+            self.dut.log.info("Waiting for onCapabilitiesChanged after"
+                              " roaming")
+            # Ensure that the roaming complete triggered a capabilities change
+            # with the new bssid.
+            autils.wait_for_event_with_keys(
+                self.dut, cconsts.EVENT_NETWORK_CALLBACK, 10,
+                (cconsts.NETWORK_CB_KEY_ID, network_cb_key),
+                (cconsts.NETWORK_CB_KEY_EVENT,
+                 cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+                (cconsts.NETWORK_CB_KEY_TRANSPORT_INFO,
+                 self.generate_wifi_info(AP2_network)))
 
-    def get_rssi(self, pcap_file, expected_bssid):
-        """Get signal strength of the wifi network attenuated.
+    ### Test Cases ###
 
-        Args:
-            pcap_file: PCAP file path.
-            expected_bssid: BSSID of the wifi network attenuated.
-        """
-        packets = []
-        try:
-            packets = rdpcap(pcap_file)
-        except Scapy_Exception:
-            self.log.error("Failed to read pcap file")
-        if not packets:
-            return 0
-
-        dbm = -100
-        for pkt in packets:
-            if pkt and hasattr(pkt, 'type') and pkt.type == 0 and \
-                pkt.subtype == 8 and hasattr(pkt, 'info'):
-                  bssid = pkt.addr3
-                  if expected_bssid == bssid:
-                      dbm = int(pkt.dBm_AntSignal)
-        self.log.info("RSSI: %s" % dbm)
-        return dbm
-
-    def trigger_roaming_and_verify_attenuation(self, network):
-        """Trigger roaming and verify signal strength is below roaming limit.
-
-        Args:
-            network: Wifi network that is being attenuated.
-        """
-        wutils.set_attns_steps(self.attenuators, "AP1_off_AP2_on")
-        band = '5G' if network['SSID'].startswith('5g_') else '2G'
-        attn = DEF_ATTN + ATTN_STEP
-        while attn <= MAX_ATTN:
-            self.pcap_procs = wutils.start_pcap(
-                self.packet_capture, 'dual', self.test_name)
-            time.sleep(WAIT_AFTER_ATTN/3)
-            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
-            pcap_file = os.path.join(
-                context.get_current_context().get_full_output_path(),
-                'PacketCapture',
-                '%s_%s.pcap' % (self.test_name, band))
-
-            rssi = self.get_rssi(pcap_file, network["bssid"])
-            if rssi == 0:
-                self.log.error("Failed to verify signal strength")
-                break
-            if self.get_rssi(pcap_file, network["bssid"]) < ROAM_DBM:
-                break
-
-            self.attenuators[0].set_atten(attn)
-            self.attenuators[1].set_atten(attn)
-            time.sleep(WAIT_AFTER_ATTN) # allow some time for attenuation
-            attn += 5
-
-    def validate_roaming(self, expected_con):
-        """Validate roaming.
-
-        Args:
-            expected_con: Expected wifi network after roaming.
-        """
-        expected_con = {
-            WifiEnums.SSID_KEY: expected_con[WifiEnums.SSID_KEY],
-            WifiEnums.BSSID_KEY: expected_con["bssid"],
-        }
-        curr_con = self.dut.droid.wifiGetConnectionInfo()
-        for key in expected_con:
-            if expected_con[key] != curr_con[key]:
-                asserts.fail("Expected '%s' to be %s, actual is %s." %
-                             (key, expected_con[key], curr_con[key]))
-        self.log.info("Roamed to %s successfully",
-                      expected_con[WifiEnums.BSSID_KEY])
-        if not wutils.validate_connection(self.dut):
-            raise signals.TestFailure("Fail to connect to internet on %s" %
-                                      expected_con[WifiEnums.BSSID_KEY])
-
-    """ Tests Begin.
-
-        The following tests are designed to test inter-SSID Roaming only.
-
-        """
     @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"]
-        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+            self.log.info("BSSID map: %s" % self.bssid_map)
+        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"]
-        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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"]
-        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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"]
-        self.roaming_from_AP1_and_AP2(AP1_network, AP2_network)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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):
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(owe_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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):
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(owe_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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):
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(sae_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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):
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(sae_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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="521269cb-5d2c-46e6-bc01-a03bd148ce28")
+    def test_soft_2g_ap_channel_when_roam_to_chan_13(self):
+        """Verify softAp 2G channel when after roaming to network on channel 13.
+
+        Steps:
+            1. Configure 2 APs - 1 on channel 6, the other on channel 13.
+            2. Connect DUT to AP on channel 6.
+            3. Start softAp on DUT on 2G band.
+            4. Verify softAp is started on channel 6.
+            5. Roam to AP2 with channel 13.
+            6. Verify SoftAp on DUT changed to channel 13.
+        """
+        for ad in self.android_devices:
+            wutils.set_wifi_country_code(
+                    ad, wutils.WifiEnums.CountryCode.AUSTRALIA)
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                ap_count=2,
+                                                channel_2g_ap2=13,
+                                                mirror_ap=True)
+        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"]]
+        wutils.set_attns(self.attenuators, "AP1_on_AP2_off", self.roaming_attn)
+        wutils.connect_to_wifi_network(self.dut, ap1_network)
+
+        # start softap on 2G and verify the channel is 6.
+        sap_config = {
+                WifiEnums.SSID_KEY: "hotspot_%s" % utils.rand_ascii_str(6),
+                WifiEnums.PWD_KEY: "pass_%s" % utils.rand_ascii_str(6),
+                WifiEnums.AP_BAND_KEY: WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G}
+        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)
+        wutils.connect_to_wifi_network(self.dut_client, sap_config)
+        conn_info = self.dut_client.droid.wifiGetConnectionInfo()
+        self.log.info("Wifi connection info on dut_client: %s" % conn_info)
+        softap_channel = wutils.WifiEnums.freq_to_channel[conn_info["frequency"]]
+        asserts.assert_true(softap_channel == 6,
+                            "Dut client did not connect to softAp on channel 6")
+
+        # trigger roaming to AP2 with channel 13
+        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)
+
+        # verify softap is now moved to channel 13
+        conn_info = self.dut_client.droid.wifiGetConnectionInfo()
+        self.log.info("Wifi connection info on dut_client: %s" % conn_info)
+        softap_channel = wutils.WifiEnums.freq_to_channel[conn_info["frequency"]]
+        asserts.assert_true(softap_channel == 13,
+                            "Dut client did not connect to softAp on channel 13")
 
     @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)
+        """Verify roaming fail with mismatch passwords.
 
-    """ Tests End """
+        Steps:
+            DUT connect to AP1.
+            Change AP2's password.
+            DUT try roaming from AP1 to AP2 with mismatched password.
+            Change AP2's password back to original one.
+            DUT try roaming from AP1 to AP2 with matched passwords.
+        """
+        # Use OpenWrt as Wi-Fi AP when it's available in testbed.
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+            self.openwrt1 = self.access_points[0]
+            self.openwrt2 = self.access_points[1]
+            ap1_network = self.reference_networks[0]["2g"]
+            ap2_network = self.reference_networks[1]["2g"]
+            # Get APs' BSSIDs.
+            ap1_bssid = self.bssid_map[0]["2g"][ap1_network["SSID"]]
+            ap2_bssid = self.bssid_map[1]["2g"][ap2_network["SSID"]]
+            # Make AP's configs.
+            ap1_network["bssid"] = ap1_bssid
+            ap2_network["bssid"] = ap2_bssid
+
+            # Change AP2 2G to password.
+            self.openwrt2.set_password(pwd_2g=utils.rand_ascii_str(8))
+
+            try:
+                # DUT roaming from AP1 to AP2 with mismatched passwords.
+                self.dut.log.info("Roaming via mismatched passwords to AP2 [{}]"
+                                  .format(ap2_bssid))
+                self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+            except:
+                self.dut.log.info("Failed roaming to AP2")
+                self.dut.log.info("Roaming via matched passwords to AP2 [{}]"
+                                  .format(ap1_bssid))
+                wutils.set_attns_steps(self.attenuators, "AP1_on_AP2_off",
+                                       self.roaming_attn)
+                self.openwrt2.set_password(pwd_2g=ap2_network["password"])
+                self.dut.log.info("Toggling wifi OFF.")
+                wutils.wifi_toggle_state(self.dut, False)
+                self.dut.log.info("Toggling wifi ON.")
+                wutils.wifi_toggle_state(self.dut, True)
+                self.roaming_from_AP1_and_AP2(ap1_network, ap2_network)
+            else:
+                raise signals.TestFailure("DUT unexpectedly connect to Wi-Fi.")
+
+        # Use Google OnHub as Wi-Fi AP to test when OpenWrt is no available.
+        elif "AccessPoint" in self.user_params:
+            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)
+
+    @test_tracker_info(uuid="b6d73094-22bc-4460-9d55-ce34a0a6a8c9")
+    def test_roaming_fail_different_bssid(self):
+        """Verify devices is disconnect with difference bssid after roaming
+
+        Steps:
+            1. Configure 2 APs
+            2. Connect DUT to AP 1
+            3. Roam to AP2
+            4. Verify the bssid is difference and the device can't connect
+            5. Verify device is disconnect after roaming.
+        """
+        if "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                ap_count=2,
+                                                mirror_ap=True)
+        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"]]
+        wutils.set_attns(self.attenuators, "AP1_on_AP2_off")
+        wutils.connect_to_wifi_network(self.dut, ap1_network)
+
+        # Initiate roaming with AP2
+        wutils.set_attns(self.attenuators, "AP1_off_AP2_on")
+        time.sleep(10)
+        try:
+            wutils.verify_wifi_connection_info(self.dut, ap2_network)
+        except:
+            self.log.info("Roaming failed to AP2 with incorrect BSSID")
+            wutils.wait_for_disconnect(self.dut)
+            self.log.info("Device is disconnect")
diff --git a/acts_tests/tests/google/wifi/WifiRssiTest.py b/acts_tests/tests/google/wifi/WifiRssiTest.py
index 3055985..1c0c6df 100644
--- a/acts_tests/tests/google/wifi/WifiRssiTest.py
+++ b/acts_tests/tests/google/wifi/WifiRssiTest.py
@@ -84,7 +84,7 @@
         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.")
+                                'Can not turn on airplane mode.')
         wutils.wifi_toggle_state(self.dut, True)
 
     def teardown_test(self):
@@ -785,17 +785,19 @@
         """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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
 
         for channel, mode, traffic_mode, test_type in itertools.product(
                 channels, modes, traffic_modes, test_types):
-            if channel not in allowed_configs[mode]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             test_name = test_type + '_ch{}_{}_{}'.format(
                 channel, mode, traffic_mode)
@@ -817,7 +819,7 @@
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
             ['test_rssi_stability', 'test_rssi_vs_atten'], [1, 2, 6, 10, 11],
-            ['VHT20'], ['ActiveTraffic'])
+            ['bw20'], ['ActiveTraffic'])
 
 
 class WifiRssi_5GHz_ActiveTraffic_Test(WifiRssiTest):
@@ -825,7 +827,7 @@
         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'],
+            [36, 40, 44, 48, 149, 153, 157, 161], ['bw20', 'bw40', 'bw80'],
             ['ActiveTraffic'])
 
 
@@ -835,7 +837,7 @@
         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'])
+            ['bw20', 'bw40', 'bw80'], ['ActiveTraffic'])
 
 
 class WifiRssi_SampleChannels_NoTraffic_Test(WifiRssiTest):
@@ -843,7 +845,7 @@
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
             ['test_rssi_stability', 'test_rssi_vs_atten'], [6, 36, 149],
-            ['VHT20', 'VHT40', 'VHT80'], ['NoTraffic'])
+            ['bw20', 'bw40', 'bw80'], ['NoTraffic'])
 
 
 class WifiRssiTrackingTest(WifiRssiTest):
@@ -851,7 +853,7 @@
         super().__init__(controllers)
         self.tests = self.generate_test_cases(['test_rssi_tracking'],
                                               [6, 36, 149],
-                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['bw20', 'bw40', 'bw80'],
                                               ['ActiveTraffic', 'NoTraffic'])
 
 
@@ -964,10 +966,10 @@
         Args:
             testcase_params: dict containing test-specific parameters
         """
-        if "rssi_over_orientation" in self.test_name:
+        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:
+        elif 'rssi_variation' in self.test_name:
             rssi_test_duration = self.testclass_params[
                 'rssi_variation_duration']
 
@@ -1015,19 +1017,21 @@
                             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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
 
         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]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             test_name = test_type + '_ch{}_{}_{}_{}deg'.format(
                 channel, mode, traffic, orientation)
@@ -1049,7 +1053,7 @@
     def __init__(self, controllers):
         super().__init__(controllers)
         self.tests = self.generate_test_cases(['test_rssi_vs_atten'],
-                                              [6, 36, 149], ['VHT20'],
+                                              [6, 36, 149], ['bw20'],
                                               ['ActiveTraffic'],
                                               ['orientation'],
                                               list(range(0, 360, 45)))
@@ -1059,7 +1063,7 @@
     def __init__(self, controllers):
         WifiRssiTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(['test_rssi_variation'],
-                                              [6, 36, 149], ['VHT20'],
+                                              [6, 36, 149], ['bw20'],
                                               ['ActiveTraffic'],
                                               ['StirrersOn'], [0])
 
@@ -1068,7 +1072,7 @@
     def __init__(self, controllers):
         WifiRssiTest.__init__(self, controllers)
         self.tests = self.generate_test_cases(['test_rssi_over_orientation'],
-                                              [6, 36, 149], ['VHT20'],
+                                              [6, 36, 149], ['bw20'],
                                               ['ActiveTraffic'],
                                               ['orientation'],
                                               list(range(0, 360, 10)))
diff --git a/acts_tests/tests/google/wifi/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
index 931cbd5..5096f93 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -62,7 +62,6 @@
         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', 'rvr_test_params', 'testbed_params',
             'RemoteServer', 'main_network'
@@ -97,10 +96,11 @@
 
         # 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)
+            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()
@@ -150,7 +150,8 @@
         try:
             throughput_limits = self.compute_throughput_limits(rvr_result)
         except:
-            asserts.fail('Test failed: Golden file not found')
+            asserts.explicit_pass(
+                'Test passed by default. Golden file not found')
 
         failure_count = 0
         for idx, current_throughput in enumerate(
@@ -233,7 +234,7 @@
         }
         return throughput_limits
 
-    def process_test_results(self, rvr_result):
+    def plot_rvr_result(self, rvr_result):
         """Saves plots and JSON formatted results.
 
         Args:
@@ -295,6 +296,7 @@
                                         '{}.html'.format(test_name))
         figure.generate_figure(output_file_path)
 
+    def compute_test_metrics(self, rvr_result):
         #Set test metrics
         rvr_result['metrics'] = {}
         rvr_result['metrics']['peak_tput'] = max(
@@ -303,9 +305,11 @@
             self.testcase_metric_logger.add_metric(
                 'peak_tput', rvr_result['metrics']['peak_tput'])
 
+        test_mode = rvr_result['ap_settings'][rvr_result['testcase_params']
+                                              ['band']]['bandwidth']
         tput_below_limit = [
-            tput < self.testclass_params['tput_metric_targets'][
-                rvr_result['testcase_params']['mode']]['high']
+            tput <
+            self.testclass_params['tput_metric_targets'][test_mode]['high']
             for tput in rvr_result['throughput_receive']
         ]
         rvr_result['metrics']['high_tput_range'] = -1
@@ -323,8 +327,8 @@
                 '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']
+            tput <
+            self.testclass_params['tput_metric_targets'][test_mode]['low']
             for tput in rvr_result['throughput_receive']
         ]
         for idx in range(len(tput_below_limit)):
@@ -338,6 +342,10 @@
             self.testcase_metric_logger.add_metric(
                 'low_tput_range', rvr_result['metrics']['low_tput_range'])
 
+    def process_test_results(self, rvr_result):
+        self.plot_rvr_result(rvr_result)
+        self.compute_test_metrics(rvr_result)
+
     def run_rvr_test(self, testcase_params):
         """Test function to run RvR.
 
@@ -352,7 +360,9 @@
         """
         self.log.info('Start running RvR')
         # Refresh link layer stats before test
-        llstats_obj = wputils.LinkLayerStats(self.dut)
+        llstats_obj = wputils.LinkLayerStats(
+            self.monitored_dut,
+            self.testclass_params.get('monitor_llstats', 1))
         zero_counter = 0
         throughput = []
         llstats = []
@@ -371,23 +381,36 @@
                 self.sniffer.start_capture(
                     network=testcase_params['test_network'],
                     chan=int(testcase_params['channel']),
-                    bw=int(testcase_params['mode'][3:]),
+                    bw=testcase_params['bandwidth'],
                     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))
-            rssi_future = wputils.get_connected_rssi_nb(
-                self.dut, self.testclass_params['iperf_duration'] - 1, 1, 1)
             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()
-            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']
-            }
+            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']:
@@ -454,9 +477,7 @@
         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:
+        if '2G' in testcase_params['band']:
             frequency = wutils.WifiEnums.channel_2G_to_freq[
                 testcase_params['channel']]
         else:
@@ -466,8 +487,10 @@
             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_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))
 
@@ -477,13 +500,15 @@
         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.dut, 20) and testcase_params['traffic_direction'] == 'UL':
+                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.dut.go_to_sleep()
-        if wputils.validate_network(self.dut,
+        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:
@@ -494,13 +519,20 @@
             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 self.testbed_params['sniffer_enable']:
+                self.sniffer.start_capture(
+                    network={'SSID': testcase_params['test_network']['SSID']},
+                    chan=testcase_params['channel'],
+                    bw=testcase_params['bandwidth'],
+                    duration=180)
+            try:
+                wutils.wifi_connect(self.sta_dut,
+                                    testcase_params['test_network'],
+                                    num_of_tries=5,
+                                    check_connectivity=True)
+            finally:
+                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.
@@ -522,12 +554,22 @@
             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'] = self.dut_ip
+            testcase_params['iperf_server_address'] = sta_dut_ip
         else:
-            testcase_params[
-                'iperf_server_address'] = wputils.get_server_address(
-                    self.remote_server, self.dut_ip, '255.255.255.0')
+            if self.testbed_params.get('lan_traffic_only', True):
+                testcase_params[
+                    'iperf_server_address'] = wputils.get_server_address(
+                        self.remote_server, sta_dut_ip, '255.255.255.0')
+            else:
+                testcase_params[
+                    'iperf_server_address'] = wputils.get_server_address(
+                        self.remote_server, sta_dut_ip, 'public')
+        # 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.
@@ -545,7 +587,18 @@
         ]
         band = self.access_point.band_lookup_by_channel(
             testcase_params['channel'])
+        testcase_params['band'] = band
         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'
@@ -553,13 +606,21 @@
             testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
                 duration=self.testclass_params['iperf_duration'],
                 reverse_direction=1,
-                traffic_type=testcase_params['traffic_type'])
+                traffic_type=testcase_params['traffic_type'],
+                socket_size=testcase_params['iperf_socket_size'],
+                num_processes=testcase_params['iperf_processes'],
+                udp_throughput=self.testclass_params['UDP_rates'][
+                    testcase_params['mode']])
             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'])
+                traffic_type=testcase_params['traffic_type'],
+                socket_size=testcase_params['iperf_socket_size'],
+                num_processes=testcase_params['iperf_processes'],
+                udp_throughput=self.testclass_params['UDP_rates'][
+                    testcase_params['mode']])
             testcase_params['use_client_output'] = False
         return testcase_params
 
@@ -586,23 +647,26 @@
         """Function that auto-generates test cases for a test class."""
         test_cases = []
         allowed_configs = {
-            'VHT20': [
+            20: [
                 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]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
 
         for channel, mode, traffic_type, traffic_direction in itertools.product(
                 channels, modes, traffic_types, traffic_directions):
-            if channel not in allowed_configs[mode]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             test_name = 'test_rvr_{}_{}_ch{}_{}'.format(
                 traffic_type, traffic_direction, channel, mode)
             test_params = collections.OrderedDict(
                 channel=channel,
                 mode=mode,
+                bandwidth=bandwidth,
                 traffic_type=traffic_type,
                 traffic_direction=traffic_direction)
             setattr(self, test_name, partial(self._test_rvr, test_params))
@@ -610,42 +674,32 @@
         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):
+class WifiRvr_TCP_Test(WifiRvrTest):
     def __init__(self, controllers):
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
-            channels=[36, 40, 44, 48],
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['bw20', 'bw40', 'bw80', 'bw160'],
+            traffic_types=['TCP'],
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiRvr_VHT_TCP_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_UNII3_Test(WifiRvrTest):
+class WifiRvr_HE_TCP_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'],
+            channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
+            modes=['HE20', 'HE40', 'HE80', 'HE160'],
             traffic_types=['TCP'],
             traffic_directions=['DL', 'UL'])
 
@@ -655,39 +709,39 @@
         super().__init__(controllers)
         self.tests = self.generate_test_cases(
             channels=[6, 36, 149],
-            modes=['VHT20', 'VHT40', 'VHT80'],
+            modes=['bw20', 'bw40', 'bw80', 'bw160'],
             traffic_types=['UDP'],
             traffic_directions=['DL', 'UL'])
 
 
-class WifiRvr_TCP_All_Test(WifiRvrTest):
+class WifiRvr_VHT_SampleUDP_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'],
+            channels=[6, 36, 149],
+            modes=['VHT20', 'VHT40', 'VHT80', 'VHT160'],
+            traffic_types=['UDP'],
             traffic_directions=['DL', 'UL'])
 
 
-class WifiRvr_TCP_Downlink_Test(WifiRvrTest):
+class WifiRvr_HE_SampleUDP_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'])
+            channels=[6, 36, 149],
+            modes=['HE20', 'HE40', 'HE80', 'HE160'],
+            traffic_types=['UDP'],
+            traffic_directions=['DL', 'UL'])
 
 
-class WifiRvr_TCP_Uplink_Test(WifiRvrTest):
+class WifiRvr_SampleDFS_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'],
+            channels=[64, 100, 116, 132, 140],
+            modes=['bw20', 'bw40', 'bw80'],
             traffic_types=['TCP'],
-            traffic_directions=['UL'])
+            traffic_directions=['DL', 'UL'])
 
 
 # Over-the air version of RVR tests
@@ -774,7 +828,7 @@
             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_key = '{}.avg_{}'.format(metric_tag, metric_key)
                 metric_value = numpy.mean(metric_value)
                 self.testclass_metric_logger.add_metric(
                     metric_key, metric_value)
@@ -806,21 +860,24 @@
                             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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
         for channel, mode, angle, traffic_type, direction in itertools.product(
                 channels, modes, angles, traffic_types, directions):
-            if channel not in allowed_configs[mode]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             testcase_name = 'test_rvr_{}_{}_ch{}_{}_{}deg'.format(
                 traffic_type, direction, channel, mode, angle)
             test_params = collections.OrderedDict(channel=channel,
                                                   mode=mode,
+                                                  bandwidth=bandwidth,
                                                   traffic_type=traffic_type,
                                                   traffic_direction=direction,
                                                   orientation=angle)
@@ -834,18 +891,17 @@
         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'])
+            ['bw20', 'bw40', 'bw80'], 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'],
+        self.tests = self.generate_test_cases([6], ['bw20'],
                                               list(range(0, 360, 45)), ['TCP'],
                                               ['DL'])
         self.tests.extend(
-            self.generate_test_cases([36, 149], ['VHT80'],
+            self.generate_test_cases([36, 149], ['bw80'],
                                      list(range(0, 360, 45)), ['TCP'], ['DL']))
 
 
@@ -853,5 +909,5 @@
     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'])
+            [6, 36, 40, 44, 48, 149, 153, 157, 161], ['bw20', 'bw40', 'bw80'],
+            [0], ['TCP'], ['DL', 'UL'])
diff --git a/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py b/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
index 548c6eb..fb75b60 100755
--- a/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
+++ b/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
@@ -226,11 +226,11 @@
         stime_channels: Dwell time plus 2ms.
         dut: Android device(s).
         wifi_chs: WiFi channels according to the device model.
-        max_bugreports: Max number of bug reports allowed.
     """
 
-    def __init__(self, controllers):
-        WifiBaseTest.__init__(self, controllers)
+    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',
@@ -241,11 +241,7 @@
             'test_wifi_scans_24GHz_5GHz_full_result',)
 
     def setup_class(self):
-        # 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)
+        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]
@@ -256,22 +252,16 @@
         """ Setup the required dependencies and fetch the user params from
         config file.
         """
-        req_params = ["max_bugreports"]
         opt_param = ["reference_networks"]
-        self.unpack_userparams(
-            req_param_names=req_params, opt_param_names=opt_param)
+        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 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)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
diff --git a/acts_tests/tests/google/wifi/WifiScannerScanTest.py b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
index 4034f76..5d987e6 100755
--- a/acts_tests/tests/google/wifi/WifiScannerScanTest.py
+++ b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
@@ -20,7 +20,6 @@
 import traceback
 
 from acts import asserts
-from acts import base_test
 from acts import utils
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.wifi import wifi_constants
@@ -45,8 +44,9 @@
 
 
 class WifiScannerScanTest(WifiBaseTest):
-    def __init__(self, controllers):
-        WifiBaseTest.__init__(self, controllers)
+    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. :(
@@ -71,15 +71,21 @@
             "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", "max_bugreports", "dbs_supported_models")
+        req_params = ("run_extended_test", "ping_addr", "dbs_supported_models",
+                      "support_addition_channel")
         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
@@ -94,22 +100,16 @@
             "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.wifi_chs = wutils.WifiChannelUS(self.dut.model, self.support_addition_channel)
         self.attenuators = wutils.group_attenuators(self.attenuators)
         self.attenuators[0].set_atten(0)
         self.attenuators[1].set_atten(0)
 
     def teardown_test(self):
-        base_test.BaseTestClass.teardown_test(self)
+        super().teardown_test()
         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
-        self.dut.cat_adb_log(test_name, begin_time)
-
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
             del self.user_params["reference_networks"]
diff --git a/acts_tests/tests/google/wifi/WifiSensitivityTest.py b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
index 558dae5..41aa32c 100644
--- a/acts_tests/tests/google/wifi/WifiSensitivityTest.py
+++ b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
@@ -162,7 +162,7 @@
         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.")
+                                'Can not turn on airplane mode.')
         wutils.wifi_toggle_state(self.dut, True)
 
         # Configure test retries
@@ -245,7 +245,7 @@
             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_key = '{}.avg_sensitivity'.format(metric_tag)
             metric_value = numpy.mean(metric_data)
             self.testclass_metric_logger.add_metric(metric_key, metric_value)
 
@@ -414,7 +414,7 @@
             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.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']][
@@ -514,7 +514,7 @@
         return testcase_params
 
     def _test_sensitivity(self, testcase_params):
-        """ Function that gets called for each test case
+        """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
@@ -549,6 +549,7 @@
                 if mode in self.VALID_TEST_CONFIGS[channel]
             ]
             for mode in requested_modes:
+                bandwidth = int(''.join([x for x in mode if x.isdigit()]))
                 if 'VHT' in mode:
                     rates = self.VALID_RATES[mode]
                 elif 'HT' in mode:
@@ -563,6 +564,7 @@
                     testcase_params = collections.OrderedDict(
                         channel=channel,
                         mode=mode,
+                        bandwidth=bandwidth,
                         rate=rate.mcs,
                         num_streams=rate.streams,
                         short_gi=1,
@@ -773,7 +775,7 @@
                 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(
+                self.log.info(('Average Sensitivity for {}: {:.1f}').format(
                     metric_tag, metric_value))
             current_context = (
                 context.get_current_context().get_full_output_path())
@@ -833,6 +835,7 @@
                 if mode in self.VALID_TEST_CONFIGS[channel]
             ]
             for chain, mode in itertools.product(chain_mask, requested_modes):
+                bandwidth = int(''.join([x for x in mode if x.isdigit()]))
                 if 'VHT' in mode:
                     valid_rates = self.VALID_RATES[mode]
                 elif 'HT' in mode:
@@ -847,6 +850,7 @@
                     testcase_params = collections.OrderedDict(
                         channel=channel,
                         mode=mode,
+                        bandwidth=bandwidth,
                         rate=rate.mcs,
                         num_streams=rate.streams,
                         short_gi=1,
diff --git a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
index 59014e8..974a784 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
@@ -15,7 +15,6 @@
 #   limitations under the License.
 
 import itertools
-import os
 import pprint
 import queue
 import sys
@@ -28,7 +27,6 @@
 
 from acts import asserts
 from acts.controllers.ap_lib import hostapd_constants
-from acts.keys import Config
 from acts.test_decorators import test_tracker_info
 from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
 from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
@@ -52,14 +50,9 @@
 
         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,
@@ -67,23 +60,16 @@
         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 = []
+        req_params = ["wifi6_models",]
         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
-        if "cnss_diag_file" in self.user_params:
-            self.cnss_diag_file = self.user_params.get("cnss_diag_file")
-            if isinstance(self.cnss_diag_file, list):
-                self.cnss_diag_file = self.cnss_diag_file[0]
-            if not os.path.isfile(self.cnss_diag_file):
-                self.cnss_diag_file = os.path.join(
-                    self.user_params[Config.key_config_path.value],
-                    self.cnss_diag_file)
 
     def setup_test(self):
+        super().setup_test()
         if hasattr(self, 'packet_capture'):
             chan = self.test_name.split('_')[-1]
             if chan.isnumeric():
@@ -91,20 +77,16 @@
                 self.packet_capture[0].configure_monitor_mode(band, int(chan))
                 self.pcap_procs = wutils.start_pcap(
                     self.packet_capture[0], band, self.test_name)
-        if hasattr(self, "cnss_diag_file"):
-            wutils.start_cnss_diags(
-                self.android_devices, self.cnss_diag_file, self.pixel_models)
         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, "cnss_diag_file"):
-            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
         if hasattr(self, 'packet_capture') and self.pcap_procs:
             wutils.stop_pcap(self.packet_capture[0], self.pcap_procs, False)
             self.pcap_procs = None
@@ -116,11 +98,6 @@
             pass
         self.access_points[0].close()
 
-    def on_fail(self, test_name, begin_time):
-        for ad in self.android_devices:
-            ad.take_bug_report(test_name, begin_time)
-            wutils.get_cnss_diag_log(ad, test_name)
-
     """Helper Functions"""
 
     def run_iperf_client(self, params):
@@ -176,7 +153,7 @@
             if frequency > 0:
                 break
             time.sleep(1) # frequency not updated yet, try again after a delay
-
+        wutils.verify_11ax_softap(self.dut, self.dut_client, self.wifi6_models)
         return hostapd_constants.CHANNEL_MAP[frequency]
 
     def configure_ap(self, channel_2g=None, channel_5g=None):
@@ -187,15 +164,20 @@
             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:
-            if not channel_2g:
-                channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
-            if not channel_5g:
-                channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
             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.
@@ -212,6 +194,8 @@
             return channel
         # Connect to the AP and start IPerf traffic, while we bring up softap.
         wutils.connect_to_wifi_network(self.dut_client, network)
+        wutils.verify_11ax_wifi_connection(
+            self.dut_client, self.wifi6_models, "wifi6_ap" in self.user_params)
         t = Thread(target=self.run_iperf_client,args=((network,self.dut_client),))
         t.setDaemon(True)
         t.start()
diff --git a/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py b/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py
index 9214bb9..af34ce9 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApMultiCountryTest.py
@@ -14,8 +14,10 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import queue
+
+
 import logging
+import queue
 import random
 import time
 import re
@@ -37,6 +39,7 @@
 from acts.controllers.ap_lib.hostapd_constants import CHANNEL_MAP
 
 
+
 WifiEnums = wutils.WifiEnums
 
 class WifiSoftApMultiCountryTest(WifiBaseTest):
@@ -46,7 +49,7 @@
         self.basetest_name = (
                 "test_full_tether_startup_auto_one_client_ping_softap_multicountry",
                 "test_full_tether_startup_2G_one_client_ping_softap_multicountry",
-                "test_full_tether_startup_5G_one_client_ping_softap_multicountry")
+                "test_full_tether_startup_5G_one_client_ping_softap_multicountry",)
         self.generate_tests()
 
     def generate_testcase(self, basetest_name, country):
@@ -58,7 +61,6 @@
         """
         base_test = getattr(self, basetest_name)
         test_tracker_uuid = ""
-
         testcase_name = 'test_%s_%s' % (basetest_name, country)
         test_case = test_tracker_info(uuid=test_tracker_uuid)(
             lambda: base_test(country))
@@ -144,6 +146,7 @@
             self.packet_capture, band, self.test_name)
         time.sleep(5)
 
+
     """ Helper Functions """
     def create_softap_config(self):
         """Create a softap config with ssid and password."""
@@ -246,6 +249,7 @@
         if hasattr(self, 'packet_capture'):
             wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
 
+
     @test_tracker_info(uuid="ae4629e6-08d5-4b51-ac34-6c2485f54df5")
     def test_full_tether_startup_5G_one_client_ping_softap_multicountry(self, country):
         """(AP) 1 Device can connect to 2G hotspot
@@ -262,6 +266,7 @@
         if hasattr(self, 'packet_capture'):
             wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
 
+
     @test_tracker_info(uuid="84a10203-cb02-433c-92a7-e8aa2348cc02")
     def test_full_tether_startup_auto_one_client_ping_softap_multicountry(self, country):
         """(AP) 1 Device can connect to hotspot
@@ -279,6 +284,7 @@
         if hasattr(self, 'packet_capture'):
             wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
 
+
     """ Tests End """
 
 
diff --git a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
index ef7790b..b116666 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
@@ -25,6 +25,7 @@
 from acts_contrib.test_utils.wifi import ota_sniffer
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
 from WifiRvrTest import WifiRvrTest
 
 AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
@@ -35,7 +36,15 @@
     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_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 = (
@@ -48,10 +57,14 @@
         This function initializes hardwares and compiles parameters that are
         common to all tests in this class.
         """
-        self.dut = self.android_devices[-1]
-        req_params = ['sap_test_params', 'testbed_params']
+        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([{
@@ -94,6 +107,8 @@
         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()
@@ -109,23 +124,45 @@
             '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_sap_rvr_test(self, testcase_params):
+    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:
-            self.dut.go_to_sleep()
+            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))
@@ -133,31 +170,41 @@
                                     sap_config[wutils.WifiEnums.SSID_KEY],
                                     sap_config[wutils.WifiEnums.PWD_KEY],
                                     testcase_params['sap_band_enum'])
-        # Set attenuator to 0 dB
-        for attenuator in self.attenuators:
-            attenuator.set_atten(0, strict=False)
         # 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.android_devices[1],
+        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)
-        testcase_params['connection_info'] = self.get_sap_connection_info()
-        testcase_params['channel'] = testcase_params['connection_info'][
-            'channel']
-        testcase_params['test_network']['channel'] = testcase_params[
-            'connection_info']['channel']
-        if testcase_params['channel'] < 13:
-            testcase_params['mode'] = 'VHT20'
-        else:
-            testcase_params['mode'] = 'VHT80'
-        testcase_params['iperf_server_address'] = testcase_params[
-            'connection_info']['ap_ip_address']
+        #self.access_point = AccessPointTuple(sap_config)
+        sap_info = self.get_sap_connection_info()
+        print("SAP Info: {}".format(sap_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
+        self.monitored_interface = None
 
     def compile_test_params(self, testcase_params):
         """Function that completes all test params based on the test name.
@@ -186,6 +233,48 @@
                 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):
@@ -209,7 +298,8 @@
             sap_band='2GHz',
             sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
             traffic_type='TCP',
-            traffic_direction='DL')
+            traffic_direction='DL',
+            dut_connected=[False, False])
         self._test_sap_rvr(testcase_params)
 
     def test_rvr_TCP_UL_2GHz(self):
@@ -217,7 +307,8 @@
             sap_band='2GHz',
             sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_2G,
             traffic_type='TCP',
-            traffic_direction='UL')
+            traffic_direction='UL',
+            dut_connected=[False, False])
         self._test_sap_rvr(testcase_params)
 
     def test_rvr_TCP_DL_5GHz(self):
@@ -225,7 +316,8 @@
             sap_band='5GHz',
             sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
             traffic_type='TCP',
-            traffic_direction='DL')
+            traffic_direction='DL',
+            dut_connected=[False, False])
         self._test_sap_rvr(testcase_params)
 
     def test_rvr_TCP_UL_5GHz(self):
@@ -233,5 +325,78 @@
             sap_band='5GHz',
             sap_band_enum=wutils.WifiEnums.WIFI_CONFIG_APBAND_5G,
             traffic_type='TCP',
-            traffic_direction='UL')
+            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
index 9510a3f..54361e9 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApTest.py
@@ -14,27 +14,26 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import logging
 import os
-import queue
-import random
 import time
 
 from acts import asserts
 from acts import utils
 from acts.keys import Config
 from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.net import socket_test_utils as sutils
-from acts_contrib.test_utils.tel import tel_defines
 from acts_contrib.test_utils.tel import tel_test_utils as tel_utils
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_AUTO
 from acts_contrib.test_utils.wifi import wifi_constants
 from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
+WIFI_CONFIG_APBAND_2G = WifiEnums.WIFI_CONFIG_APBAND_2G
+WIFI_CONFIG_APBAND_5G = WifiEnums.WIFI_CONFIG_APBAND_5G
+WIFI_CONFIG_APBAND_AUTO = WifiEnums.WIFI_CONFIG_APBAND_AUTO
+WPA3_SAE_TRANSITION_SOFTAP = WifiEnums.SoftApSecurityType.WPA3_SAE_TRANSITION
+WPA3_SAE_SOFTAP = WifiEnums.SoftApSecurityType.WPA3_SAE
+WAIT_AFTER_REBOOT = 10
+
 
 class WifiSoftApTest(WifiBaseTest):
 
@@ -45,15 +44,19 @@
         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", "pixel_models"]
-        opt_param = ["open_network"]
+        req_params = ["dbs_supported_models", "sta_concurrency_supported_models",
+                      "wifi6_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()
-        self.open_network = self.open_network[0]["2g"]
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(wpa_network=True)
+        self.wifi_network = self.reference_networks[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
@@ -62,9 +65,6 @@
         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,
@@ -77,21 +77,24 @@
         self.AP_IFACE = 'wlan0'
         if self.dut.model in self.dbs_supported_models:
             self.AP_IFACE = 'wlan1'
+        if self.dut.model in self.sta_concurrency_supported_models:
+            self.AP_IFACE = 'wlan2'
         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]
-        if "cnss_diag_file" in self.user_params:
-            self.cnss_diag_file = self.user_params.get("cnss_diag_file")
-            if isinstance(self.cnss_diag_file, list):
-                self.cnss_diag_file = self.cnss_diag_file[0]
-            if not os.path.isfile(self.cnss_diag_file):
-                self.cnss_diag_file = os.path.join(
+        self.country_code = wutils.WifiEnums.CountryCode.US
+        if hasattr(self, "country_code_file"):
+            if isinstance(self.country_code_file, list):
+                self.country_code_file = self.country_code_file[0]
+            if not os.path.isfile(self.country_code_file):
+                self.country_code_file = os.path.join(
                     self.user_params[Config.key_config_path.value],
-                    self.cnss_diag_file)
+                    self.country_code_file)
+            self.country_code = utils.load_config(
+                self.country_code_file)["country"]
 
     def teardown_class(self):
         if self.dut.droid.wifiIsApEnabled():
@@ -103,26 +106,31 @@
             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)
-        if hasattr(self, "cnss_diag_file"):
-            wutils.start_cnss_diags(
-                self.android_devices, self.cnss_diag_file, self.pixel_models)
+        if "chan_13" in self.test_name and "OpenWrtAP" in self.user_params:
+            self.access_points[0].close()
+            self.configure_openwrt_ap_and_start(wpa_network=True,
+                                                channel_2g=13)
+            self.wifi_network = self.reference_networks[0]["2g"]
+            for ad in self.android_devices:
+                wutils.set_wifi_country_code(
+                        ad, wutils.WifiEnums.CountryCode.AUSTRALIA)
 
     def teardown_test(self):
-        if hasattr(self, "cnss_diag_file"):
-            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+        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)
-
-    def on_fail(self, test_name, begin_time):
-        for ad in self.android_devices:
-            ad.take_bug_report(test_name, begin_time)
-            ad.cat_adb_log(test_name, begin_time)
-            wutils.get_cnss_diag_log(ad, test_name)
+        if "chan_13" in self.test_name and "OpenWrtAP" in self.user_params:
+            self.access_points[0].close()
+            self.configure_openwrt_ap_and_start(wpa_network=True)
+            self.wifi_network = self.reference_networks[0]["2g"]
+            for ad in self.android_devices:
+                wutils.set_wifi_country_code(ad, self.country_code)
 
     """ Helper Functions """
     def create_softap_config(self):
@@ -168,7 +176,8 @@
                                 "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_ping=False, test_clients=None,
+                                     security=None):
         """Test full startup of wifi tethering
 
         1. Report current state.
@@ -186,7 +195,10 @@
         config = self.create_softap_config()
         wutils.start_wifi_tethering(self.dut,
                                     config[wutils.WifiEnums.SSID_KEY],
-                                    config[wutils.WifiEnums.PWD_KEY], band, hidden)
+                                    config[wutils.WifiEnums.PWD_KEY],
+                                    band,
+                                    hidden,
+                                    security)
         if hidden:
             # First ensure it's not seen in scan results.
             self.confirm_softap_not_in_scan_results(
@@ -224,6 +236,7 @@
 
         dut_ip = self.dut.droid.connectivityGetIPv4Addresses(self.AP_IFACE)[0]
         dut_client_ip = self.dut_client.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        wutils.verify_11ax_softap(self.dut, self.dut_client, self.wifi6_models)
 
         self.dut.log.info("Try to ping %s" % dut_client_ip)
         asserts.assert_true(
@@ -266,6 +279,21 @@
             utils.adb_shell_ping(ad2, count=10, dest_ip=ad1_ip, timeout=20),
             "%s ping %s failed" % (ad2.serial, ad1_ip))
 
+    def validate_softap_after_reboot(self, band, security, hidden=False):
+        config = self.create_softap_config()
+        softap_config = config.copy()
+        softap_config[WifiEnums.AP_BAND_KEY] = band
+        softap_config[WifiEnums.SECURITY] = security
+        if hidden:
+            softap_config[WifiEnums.HIDDEN_KEY] = hidden
+        asserts.assert_true(
+            self.dut.droid.wifiSetWifiApConfiguration(softap_config),
+            "Failed to update WifiAp Configuration")
+        self.dut.reboot()
+        time.sleep(WAIT_AFTER_REBOOT)
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        wutils.connect_to_wifi_network(self.dut_client, config, hidden=hidden)
+
     """ Tests Begin """
 
     @test_tracker_info(uuid="495f1252-e440-461c-87a7-2c45f369e129")
@@ -370,6 +398,310 @@
         """
         self.validate_full_tether_startup(WIFI_CONFIG_APBAND_AUTO, True)
 
+    @test_tracker_info(uuid="25996696-e9c8-4cd3-816a-44536166e69f")
+    def test_full_tether_startup_wpa3(self):
+        """Test full startup of softap in default band and wpa3 security.
+
+        Steps:
+        1. Configure softap in default band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="65ecdd4b-857e-4bda-87e7-3db578cee7aa")
+    def test_full_tether_startup_2G_wpa3(self):
+        """Test full startup of softap in 2G band and wpa3 security.
+
+        Steps:
+        1. Configure softap in 2G band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_2G, security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="dbc788dc-bf11-48aa-b88f-c2ee767cd13d")
+    def test_full_tether_startup_5G_wpa3(self):
+        """Test full startup of softap in 5G band and wpa3 security.
+
+        Steps:
+        1. Configure softap in 5G band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_5G, security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="1192c522-824a-4a79-a6cd-bd63b7d19e82")
+    def test_full_tether_startup_auto_wpa3(self):
+        """Test full startup of softap in auto band and wpa3 security.
+
+        Steps:
+        1. Configure softap in auto band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_AUTO, security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="120d2dcb-ded6-40d3-854c-366c200c8deb")
+    def test_full_tether_startup_hidden_wpa3(self):
+        """Test full startup of hidden softap in default band and wpa3 security.
+
+        Steps:
+        1. Configure hidden softap in default band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(security=WPA3_SAE_SOFTAP, hidden=True)
+
+    @test_tracker_info(uuid="82fc2329-480b-4cab-bf9d-e1c397673e4a")
+    def test_full_tether_startup_2G_hidden_wpa3(self):
+        """Test full startup of hidden softap in 2G band and wpa3 security.
+
+        Steps:
+        1. Configure hidden softap in 2G band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_2G, True, security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="0da2958e-de0b-4567-aff1-d4ba5439eb4e")
+    def test_full_tether_startup_5G_hidden_wpa3(self):
+        """Test full startup of hidden softap in 5G band and wpa3 security.
+
+        Steps:
+        1. Configure hidden softap in 5G band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_5G, True, security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="1412f928-e89b-4e84-8ad0-1b14e936b239")
+    def test_full_tether_startup_auto_hidden_wpa3(self):
+        """Test full startup of hidden softap in auto band and wpa3 security.
+
+        Steps:
+        1. Configure hidden softap in auto band and wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_AUTO, True, security=WPA3_SAE_SOFTAP)
+
+    @test_tracker_info(uuid="e1433f7e-57f6-4475-822c-754d77817bbc")
+    def test_full_tether_startup_wpa2_wpa3(self):
+        """Test full startup of softap in default band and wpa2/wpa3 security.
+
+        Steps:
+        1. Configure softap in default band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_full_tether_startup(security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="8f55209f-0b9a-4600-a416-84d075c349af")
+    def test_full_tether_startup_2G_wpa2_wpa3(self):
+        """Test full startup of softap in 2G band and wpa2/wpa3 security.
+
+        Steps:
+        1. Configure softap in default band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_2G, security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="3d54b7c5-cc34-473f-b484-fc3bf1773a90")
+    def test_full_tether_startup_5G_wpa2_wpa3(self):
+        """Test full startup of softap in 5G band and wpa2/wpa3 security.
+
+        Steps:
+        1. Configure softap in default band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_5G, security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="f07bedd2-d768-497d-8922-2e5fe1cd9365")
+    def test_full_tether_startup_auto_wpa2_wpa3(self):
+        """Test full startup of softap in auto band and wpa2/wpa3 security.
+
+        Steps:
+        1. Configure softap in default band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_AUTO, security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="9a54f97f-eaca-4a64-ad20-f9e52f8b16a1")
+    def test_full_tether_startup_2G_hidden_wpa2_wpa3(self):
+        """Test full startup of hidden softap in 2G band and wpa2/wpa3.
+
+        Steps:
+        1. Configure hidden softap in 2G band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_2G, True, security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="1baef45f-c6c9-46bc-8227-1aacf410e60d")
+    def test_full_tether_startup_5G_hidden_wpa2_wpa3(self):
+        """Test full startup of hidden softap in 5G band and wpa2/wpa3 security.
+
+        Steps:
+        1. Configure hidden softap in 5G band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_5G, True, security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="1976ac84-f967-4961-bdb9-4fcfe297fe22")
+    def test_full_tether_startup_auto_hidden_wpa2_wpa3(self):
+        """Test full startup of hidden softap in auto band and wpa2/wpa3.
+
+        Steps:
+        1. Configure hidden softap in auto band and wpa2/wpa3 security.
+        2. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_full_tether_startup(
+            WIFI_CONFIG_APBAND_AUTO, True, security=WPA3_SAE_TRANSITION_SOFTAP)
+
+    @test_tracker_info(uuid="dd4c79dc-169f-4d8f-a700-95ba2923af35")
+    def test_softap_wpa3_2g_after_reboot(self):
+        """Test full startup of softap in 2G band, wpa3 security after reboot.
+
+        Steps:
+        1. Save softap in 2G band and wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_2G, WPA3_SAE_SOFTAP, False)
+
+    @test_tracker_info(uuid="02f080d8-91e7-4363-a291-da3c87e74758")
+    def test_softap_wpa3_5g_after_reboot(self):
+        """Test full startup of softap in 5G band, wpa3 security after reboot.
+
+        Steps:
+        1. Save softap in 5G band and wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_5G, WPA3_SAE_SOFTAP, False)
+
+    @test_tracker_info(uuid="2224f94e-88e8-4ebf-bbab-f78ab24cefda")
+    def test_softap_wpa2_wpa3_2g_after_reboot(self):
+        """Test softap in 2G band, wpa2/wpa3 security after reboot.
+
+        Steps:
+        1. Save softap in 2G band and wpa2/wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_2G, WPA3_SAE_TRANSITION_SOFTAP, False)
+
+    @test_tracker_info(uuid="320643e2-9e13-4c8c-a2cb-1903b6bd3741")
+    def test_softap_wpa2_wpa3_5g_after_reboot(self):
+        """Test softap in 5G band, wpa2/wpa3 security after reboot.
+
+        Steps:
+        1. Save softap in 5G band and wpa2/wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_5G, WPA3_SAE_TRANSITION_SOFTAP, False)
+
+    @test_tracker_info(uuid="52005efc-45a2-41df-acd1-5fd551e88a3c")
+    def test_softap_wpa3_2g_hidden_after_reboot(self):
+        """Test hidden softap in 2G band, wpa3 security after reboot.
+
+        Steps:
+        1. Save hidden softap in 2G band and wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_2G, WPA3_SAE_SOFTAP, True)
+
+    @test_tracker_info(uuid="78e07c03-f628-482d-b78b-84bdfba0bfaf")
+    def test_softap_wpa3_5g_hidden_after_reboot(self):
+        """Test hidden softap in 5G band, wpa3 security after reboot.
+
+        Steps:
+        1. Save hidden softap in 5G band and wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_5G, WPA3_SAE_SOFTAP, True)
+
+    @test_tracker_info(uuid="749ba522-dd1f-459a-81bd-957943201c32")
+    def test_softap_wpa2_wpa3_2g_hidden_after_reboot(self):
+        """Test hidden softap in 2G band, wpa2/wpa3 security after reboot.
+
+        Steps:
+        1. Save hidden softap in 2G band and wpa2/wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_2G, WPA3_SAE_TRANSITION_SOFTAP, True)
+
+    @test_tracker_info(uuid="638f1456-8556-448d-8cad-63e6b72337ca")
+    def test_softap_wpa2_wpa3_5g_hidden_after_reboot(self):
+        """Test hidden softap in 5G band, wpa2/wpa3 security after reboot.
+
+        Steps:
+        1. Save hidden softap in 5G band and wpa2/wpa3 security.
+        2. Reboot device and start softap.
+        3. Verify dut client connects to the softap.
+        """
+        asserts.skip_if(self.dut.model not in self.sta_concurrency_supported_models,
+                        "DUT does not support WPA2/WPA3 softAp")
+        self.validate_softap_after_reboot(
+            WIFI_CONFIG_APBAND_5G, WPA3_SAE_TRANSITION_SOFTAP, 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
@@ -383,7 +715,7 @@
         6. Ensure that the client disconnected.
         """
         wutils.wifi_toggle_state(self.dut, True)
-        wutils.wifi_connect(self.dut, self.open_network)
+        wutils.wifi_connect(self.dut, self.wifi_network)
         config = self.create_softap_config()
         wutils.start_wifi_tethering(self.dut,
                                     config[wutils.WifiEnums.SSID_KEY],
@@ -394,6 +726,7 @@
         # 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.verify_11ax_softap(self.dut, self.dut_client, self.wifi6_models)
         wutils.stop_wifi_tethering(self.dut)
         wutils.wait_for_disconnect(self.dut_client)
 
@@ -668,7 +1001,9 @@
         1. Get current softap configuration
         2. Update to Open Security configuration
         3. Update to WPA2_PSK configuration
-        4. Restore the configuration
+        4. Update to Multi-Channels, Mac Randomization off,
+           bridged_shutdown off, 11ax off configuration which are introduced in S.
+        5. Restore the configuration
       """
       # Backup config
       original_softap_config = self.dut.droid.wifiGetApConfiguration()
@@ -686,6 +1021,13 @@
           shutdown_timeout_millis=10000, client_control_enable=False,
           allowedList=["aa:bb:cc:dd:ee:ff"], blockedList=["11:22:33:44:55:66"])
 
+      if self.dut.droid.isSdkAtLeastS():
+          wutils.save_wifi_soft_ap_config(self.dut, {"SSID":"ACTS_TEST"},
+              channel_frequencys=[2412,5745],
+              mac_randomization_setting = wifi_constants.SOFTAP_RANDOMIZATION_NONE,
+              bridged_opportunistic_shutdown_enabled=False,
+              ieee80211ax_enabled=False)
+
       # Restore config
       wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
 
@@ -854,6 +1196,67 @@
 
         # 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)
+        wutils.verify_11ax_softap(self.dut, self.dut_client, self.wifi6_models)
+
+    @test_tracker_info(uuid="dbcd653c-ec65-400f-a6ce-77bb13add473")
+    def test_softp_2g_channel_when_connected_to_chan_13(self):
+        """Verify softAp 2G channel when connected to network on channel 13.
+
+        Steps:
+            1. Configure AP in channel 13 on 2G band and connect DUT to it.
+            2. Start softAp on DUT on 2G band.
+            3. Verify softAp is started on channel 13.
+        """
+        asserts.skip_if("OpenWrtAP" not in self.user_params,
+                        "Need openwrt AP to configure channel 13.")
+        wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+        sap_config = self.create_softap_config()
+        sap_config[WifiEnums.AP_BAND_KEY] = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G
+        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)
+        wutils.connect_to_wifi_network(self.dut_client, sap_config)
+        softap_channel = self.dut_client.droid.wifiGetConnectionInfo(
+                )
+        conn_info = self.dut_client.droid.wifiGetConnectionInfo()
+        self.log.info("Wifi connection info on dut_client: %s" % conn_info)
+        softap_channel = wutils.WifiEnums.freq_to_channel[conn_info["frequency"]]
+        asserts.assert_true(softap_channel == 13,
+                            "Dut client did not connect to softAp on channel 13")
+
     """ Tests End """
 
 
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
index 643a33c..846d57c 100644
--- a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
@@ -69,6 +69,7 @@
             ad.droid.wifiEnableVerboseLogging(1)
 
         req_params = ["dbs_supported_models",
+                      "wifi6_models",
                       "iperf_server_address",
                       "iperf_server_port"]
         self.unpack_userparams(req_param_names=req_params,)
@@ -97,16 +98,17 @@
         self.turn_location_on_and_scan_toggle_on()
         wutils.wifi_toggle_state(self.dut, True)
         self.access_points[0].close()
-        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")
+        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):
+    def configure_ap(self, hidden=False, channel_2g=None, channel_5g=None):
         """Configure and bring up AP on required channel.
 
         Args:
@@ -118,8 +120,15 @@
             channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
         if not channel_5g:
             channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
-        self.legacy_configure_ap_and_start(channel_2g=channel_2g,
-                                           channel_5g=channel_5g)
+        if "AccessPoint" in self.user_params:
+            self.legacy_configure_ap_and_start(channel_2g=channel_2g,
+                                               channel_5g=channel_5g,
+                                               hidden=hidden)
+        elif "OpenWrtAP" in self.user_params:
+            self.configure_openwrt_ap_and_start(open_network=True,
+                                                channel_2g=channel_2g,
+                                                channel_5g=channel_5g,
+                                                hidden=hidden)
         self.open_2g = self.open_network[0]["2g"]
         self.open_5g = self.open_network[0]["5g"]
 
@@ -185,9 +194,12 @@
         for ad in self.android_devices[1:]:
             wutils.connect_to_wifi_network(
                 ad, config, check_connectivity=check_connectivity)
+            wutils.verify_11ax_softap(self.dut, ad, self.wifi6_models)
         return config
 
-    def connect_to_wifi_network_and_start_softap(self, nw_params, softap_band):
+    def connect_to_wifi_network_and_start_softap(self, nw_params,
+                                                 softap_band,
+                                                 hidden=False):
         """Test concurrent wifi connection and softap.
 
         This helper method first makes a wifi connection and then starts SoftAp.
@@ -200,7 +212,9 @@
             nw_params: Params for network STA connection.
             softap_band: Band for the AP.
         """
-        wutils.connect_to_wifi_network(self.dut, nw_params)
+        wutils.connect_to_wifi_network(self.dut, nw_params, hidden=hidden)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_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))
@@ -231,6 +245,8 @@
         """
         softap_config = self.start_softap_and_verify(softap_band, False)
         wutils.connect_to_wifi_network(self.dut, nw_params)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         self.run_iperf_client((nw_params, self.dut))
         self.run_iperf_client((softap_config, self.dut_client))
 
@@ -426,3 +442,18 @@
             self.open_2g, WIFI_CONFIG_APBAND_2G)
         self.softap_change_band(self.dut)
 
+    @test_tracker_info(uuid="d549a18e-73d9-45e7-b4df-b59446c4b833")
+    def test_wifi_connection_hidden_2g_softap_2G_to_softap_5g(self):
+        """Test connection to a hidden 2G network on Channel 1 and
+        followed by SoftAp on 2G, and switch SoftAp to 5G.
+        1. Connect to a hidden 2.4G Wi-Fi AP(channel 1)
+        2. DUT turn on 2.4G hotspot and client connect to DUT
+        3. Change AP Band of DUT Hotspot from 2.4GHz to 5GHz
+        Expected results: Both DUT and Hotspot client can access the Internet
+        """
+        self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G, hidden=True)
+        self.connect_to_wifi_network_and_start_softap(
+            self.open_2g,
+            WIFI_CONFIG_APBAND_2G,
+            hidden=True)
+        self.softap_change_band(self.dut)
diff --git a/acts_tests/tests/google/wifi/WifiStaConcurrencyNetworkRequestTest.py b/acts_tests/tests/google/wifi/WifiStaConcurrencyNetworkRequestTest.py
new file mode 100755
index 0000000..2c56137
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiStaConcurrencyNetworkRequestTest.py
@@ -0,0 +1,433 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2021 - 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.asserts as asserts
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts_contrib.test_utils.net.connectivity_const as cconsts
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.aware.aware_test_utils as autils
+
+WifiEnums = wutils.WifiEnums
+
+WIFI_NETWORK_AP_CHANNEL_2G_1 = 1
+WIFI_NETWORK_AP_CHANNEL_5G_1 = 36
+WIFI_NETWORK_AP_CHANNEL_5G_DFS_1 = 132
+
+WIFI_NETWORK_AP_CHANNEL_2G_2 = 2
+WIFI_NETWORK_AP_CHANNEL_5G_2 = 40
+WIFI_NETWORK_AP_CHANNEL_5G_DFS_2 = 136
+
+class WifiStaConcurrencyNetworkRequestTest(WifiBaseTest):
+    """STA + STA Tests for concurrency between intenet connectivity &
+    peer to peer connectivity using 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
+        self.p2p_key = None
+
+    def setup_class(self):
+        super().setup_class()
+
+        self.dut = self.android_devices[0]
+        wutils.wifi_test_device_init(self.dut)
+        req_params = ["sta_concurrency_supported_models", "wifi6_models"]
+        opt_param = [
+            "open_network", "reference_networks"
+        ]
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_param)
+
+        asserts.abort_class_if(
+                self.dut.model not in self.sta_concurrency_supported_models,
+                "Device %s doesn't support STA+STA, skipping tests")
+
+        asserts.abort_class_if(
+            "OpenWrtAP" not in self.user_params,
+            "Setup doesn't support OpenWrt AP, skipping tests")
+
+
+    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.disconnect_both()
+        self.dut.droid.wakeLockRelease()
+        self.dut.droid.goToSleepNow()
+        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()
+        # Reset access point state.
+        for ap in self.access_points:
+            ap.close()
+
+    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 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")
+
+    def register_network_callback_for_internet(self):
+        self.dut.log.debug("Registering network callback for wifi internet connectivity")
+        network_request = {
+            cconsts.NETWORK_CAP_TRANSPORT_TYPE_KEY :
+                cconsts.NETWORK_CAP_TRANSPORT_WIFI,
+            cconsts.NETWORK_CAP_CAPABILITY_KEY :
+                [cconsts.NETWORK_CAP_CAPABILITY_INTERNET]
+        }
+        key = self.dut.droid.connectivityRegisterNetworkCallback(network_request)
+        return key
+
+    def connect_to_internet_and_wait_for_on_available(self, network):
+        self.dut.log.info("Triggering internet connection after registering "
+                           "network callback")
+        self.internet_request_key = (
+            self.register_network_callback_for_internet())
+        wutils.connect_to_wifi_network(self.dut, network)
+        # Ensure that the internet connection completed and we got the
+        # ON_AVAILABLE callback.
+        autils.wait_for_event_with_keys(
+            self.dut,
+            cconsts.EVENT_NETWORK_CALLBACK,
+            20,
+            (cconsts.NETWORK_CB_KEY_ID, self.internet_request_key),
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_AVAILABLE))
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+
+    def connect_to_p2p_and_wait_for_on_available(self, network):
+        self.p2p_key = wutils.wifi_connect_using_network_request(self.dut,
+                                                                 network,
+                                                                 network)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+
+    def ensure_both_connections_are_active(self):
+        self.dut.log.info("Ensuring both connections are active")
+        network_caps = (
+            self.dut.droid.connectivityNetworkGetAllCapabilities())
+        self.dut.log.info("Active network caps: %s", network_caps)
+        num_wifi_networks = 0
+        for network_cap in network_caps:
+            transport_types = (
+                network_cap[cconsts.NETWORK_CAP_TRANSPORT_TYPE_KEY])
+            if cconsts.NETWORK_CAP_TRANSPORT_WIFI in transport_types:
+              num_wifi_networks += 1
+              self.dut.log.info("Wifi connection %s: %s",
+                                num_wifi_networks, network_cap)
+        asserts.assert_equal(2, num_wifi_networks, "Expected 2 wifi networks")
+
+
+    def ensure_both_connections_are_active_and_dont_disconnect(self):
+        self.ensure_both_connections_are_active()
+
+        # Don't use the key_id in event to ensure there are no disconnects
+        # from either connection.
+        self.dut.log.info("Ensuring no connection loss")
+        autils.fail_on_event_with_keys(
+            self.dut,
+            cconsts.EVENT_NETWORK_CALLBACK,
+            20,
+            (cconsts.NETWORK_CB_KEY_EVENT, cconsts.NETWORK_CB_LOST))
+
+    def disconnect_both(self):
+        self.dut.log.info("Disconnecting both connections")
+        if self.p2p_key:
+            asserts.assert_true(
+                self.dut.droid.connectivityUnregisterNetworkCallback(
+                    self.p2p_key),
+                "Failed to release the p2p request")
+            self.p2p_key = None
+        self.dut.droid.wifiDisconnect()
+
+    def configure_ap(self,
+                     channel_2g=None,
+                     channel_5g=None,
+                     channel_2g_ap2=None,
+                     channel_5g_ap2=None,
+                     mirror_ap=False,
+                     ap_count=1):
+        """Configure ap based on test requirements."""
+        if ap_count==1:
+            self.configure_openwrt_ap_and_start(
+                wpa_network=True,
+                channel_2g=WIFI_NETWORK_AP_CHANNEL_2G_1,
+                channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_1,
+                ap_count=1)
+        elif ap_count == 2 and channel_2g_ap2:
+            self.configure_openwrt_ap_and_start(
+                wpa_network=True,
+                channel_2g=WIFI_NETWORK_AP_CHANNEL_2G_1,
+                channel_2g_ap2=WIFI_NETWORK_AP_CHANNEL_2G_2,
+                mirror_ap=mirror_ap,
+                ap_count=2)
+        elif ap_count == 2 and channel_5g_ap2:
+            self.configure_openwrt_ap_and_start(
+                wpa_network=True,
+                channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_1,
+                channel_5g_ap2=WIFI_NETWORK_AP_CHANNEL_5G_2,
+                mirror_ap=mirror_ap,
+                ap_count=2)
+
+    @test_tracker_info(uuid="64a6c35f-d45d-431f-83e8-7fcfaef943e2")
+    def test_connect_to_2g_p2p_while_connected_to_5g_internet(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 5G & 2G band WPA-PSK networks.
+        2. Connect to WPA-PSK 5G network for internet connectivity.
+        3. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 2G network.
+        4. Wait for platform to scan and find matching networks.
+        5. Simulate user selecting the network.
+        6. Ensure that the device connects to the network.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_2g=WIFI_NETWORK_AP_CHANNEL_2G_1,
+            channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_1,
+            ap_count=1)
+
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[0]["5g"])
+        self.connect_to_p2p_and_wait_for_on_available(
+            self.wpa_networks[0]["2g"])
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
+
+
+    @test_tracker_info(uuid="aa8af713-5d97-4f05-8104-e697f0055d6e")
+    def test_connect_to_2g_internet_while_connected_to_5g_p2p(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 5G & 2G band WPA-PSK networks.
+        2. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 5G network.
+        3. Wait for platform to scan and find matching networks.
+        4. Simulate user selecting the network.
+        5. Ensure that the device connects to the network.
+        6. Connect to WPA-PSK 2G network for internet connectivity.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_2g=WIFI_NETWORK_AP_CHANNEL_2G_1,
+            channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_1,
+            ap_count=1)
+
+        self.connect_to_p2p_and_wait_for_on_available(
+            self.wpa_networks[0]["5g"])
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[0]["2g"])
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
+
+
+    @test_tracker_info(uuid="64dd09a7-28f6-4000-b1a1-10302922641b")
+    def test_connect_to_2g_internet_while_connected_to_2g_p2p(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 2 5G & 2G band WPA-PSK networks.
+        2. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 2G network.
+        3. Wait for platform to scan and find matching networks.
+        4. Simulate user selecting the network.
+        5. Ensure that the device connects to the network.
+        6. Connect to WPA-PSK 2G network for internet connectivity.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_2g=WIFI_NETWORK_AP_CHANNEL_2G_1,
+            channel_2g_ap2=WIFI_NETWORK_AP_CHANNEL_2G_2,
+            ap_count=2)
+
+        self.connect_to_p2p_and_wait_for_on_available(
+            self.wpa_networks[0]["2g"])
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[1]["2g"])
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
+
+
+    @test_tracker_info(uuid="0c3df2f1-7311-4dd2-b0dc-1de4bd731495")
+    def test_connect_to_5g_internet_while_connected_to_5g_p2p(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 2 5G & 2G band WPA-PSK networks.
+        2. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 5G network.
+        3. Wait for platform to scan and find matching networks.
+        4. Simulate user selecting the network.
+        5. Ensure that the device connects to the network.
+        6. Connect to WPA-PSK 5G network for internet connectivity.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_1,
+            channel_5g_ap2=WIFI_NETWORK_AP_CHANNEL_5G_2,
+            ap_count=2)
+
+        self.connect_to_p2p_and_wait_for_on_available(
+            self.wpa_networks[0]["5g"])
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[1]["5g"])
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
+
+    @test_tracker_info(uuid="be22f7f8-b761-4f40-8d10-ff802761cb8b")
+    def test_connect_to_5g_dfs_internet_while_connected_to_5g_dfs_p2p(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 2 5G-DFS & 2G band WPA-PSK networks.
+        2. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 5G-DFS network.
+        3. Wait for platform to scan and find matching networks.
+        4. Simulate user selecting the network.
+        5. Ensure that the device connects to the network.
+        6. Connect to WPA-PSK 5G network for internet connectivity.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS_1,
+            channel_5g_ap2=WIFI_NETWORK_AP_CHANNEL_5G_DFS_2,
+            ap_count=2)
+
+        self.connect_to_p2p_and_wait_for_on_available(
+            self.wpa_networks[0]["5g"])
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[1]["5g"])
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
+
+    @test_tracker_info(uuid="dc390b3f-2856-4c96-880b-9732e3dc228f")
+    def test_connect_to_2g_internet_while_connected_to_2g_p2p_same_ssid(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 2 5G & 2G band WPA-PSK networks with the same SSID.
+        2. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 2G network.
+        3. Wait for platform to scan and find matching networks.
+        4. Simulate user selecting the network.
+        5. Ensure that the device connects to the network.
+        6. Connect to WPA-PSK 2G network for internet connectivity.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_2g=WIFI_NETWORK_AP_CHANNEL_2G_1,
+            channel_2g_ap2=WIFI_NETWORK_AP_CHANNEL_2G_2,
+            mirror_ap=True,
+            ap_count=2)
+
+        self.connect_to_p2p_and_wait_for_on_available(
+            self.wpa_networks[0]["2g"])
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[0]["2g"])
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
+
+    @test_tracker_info(uuid="033d324d-94e0-440e-9532-993bc2682269")
+    def test_connect_to_5g_p2p_while_connected_to_5g_internet_same_ssid(self):
+        """
+        Initiates a connection to a peer to peer network via network request while
+        already connected to an internet connectivity network.
+
+        Steps:
+        1. Setup 2 5G & 2G band WPA-PSK networks with the same SSID.
+        2. Connect to WPA-PSK 5G network for internet connectivity.
+        3. Send a network specifier with the specific SSID/credentials of
+           WPA-PSK 5G network (with the other bssid).
+        4. Wait for platform to scan and find matching networks.
+        5. Simulate user selecting the network.
+        6. Ensure that the device connects to the network.
+        7. Ensure that the device remains connected to both the networks.
+        8. Disconnect both connections.
+        """
+        self.configure_ap(
+            channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_1,
+            channel_5g_ap2=WIFI_NETWORK_AP_CHANNEL_5G_2,
+            mirror_ap=True,
+            ap_count=2)
+
+        self.connect_to_internet_and_wait_for_on_available(
+            self.wpa_networks[0]["5g"])
+
+        ssid = self.wpa_networks[0]["5g"][WifiEnums.SSID_KEY]
+        bssids = [self.bssid_map[0]["5g"][ssid], self.bssid_map[1]["5g"][ssid]]
+        # Find the internet connection bssid.
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        connected_bssid = wifi_info[WifiEnums.BSSID_KEY].upper()
+        # Find the bssid of the other access point with same ssid.
+        p2p_bssid = set(bssids) - {connected_bssid}
+
+        # Construct specifier for the other bssid.
+        network_specifier_with_bssid = self.wpa_networks[0]["5g"].copy()
+        network_specifier_with_bssid[WifiEnums.BSSID_KEY] = next(iter(p2p_bssid))
+        self.connect_to_p2p_and_wait_for_on_available(
+            network_specifier_with_bssid)
+
+        self.ensure_both_connections_are_active_and_dont_disconnect()
diff --git a/acts_tests/tests/google/wifi/WifiStressTest.py b/acts_tests/tests/google/wifi/WifiStressTest.py
index d02d78ab..87417f8 100644
--- a/acts_tests/tests/google/wifi/WifiStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiStressTest.py
@@ -46,6 +46,9 @@
     * 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()
@@ -68,7 +71,10 @@
 
         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,
+                                                ap_count=2)
         asserts.assert_true(
             len(self.reference_networks) > 0,
             "Need at least one reference network with psk.")
@@ -79,20 +85,18 @@
         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 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)
-
     def teardown_class(self):
         wutils.reset_wifi(self.dut)
         if "AccessPoint" in self.user_params:
@@ -334,6 +338,7 @@
         raise signals.TestPass(details="", extras={"Total Hours":"%d" %
             self.stress_hours, "Seconds Run":"%d" %total_time})
 
+    @test_tracker_info(uuid="591d257d-9477-4a89-a220-5715c93a76a7")
     def test_stress_youtube_5g(self):
         """Test to connect to network and play various youtube videos.
 
diff --git a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
index 9e93a9d..6619ba4 100644
--- a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
+++ b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
@@ -14,6 +14,13 @@
 from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general
 from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_idle_iwlan
+from acts_contrib.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
+from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_defines import GEN_4G
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
+from acts_contrib.test_utils.net import net_test_utils as nutil
 
 WifiEnums = wifi_utils.WifiEnums
 
@@ -46,11 +53,18 @@
 
 
     def setup_test(self):
+        """ Setup test make sure the DUT is wake and screen unlock"""
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
         wifi_utils.wifi_toggle_state(self.dut, True)
 
 
     def teardown_test(self):
-        wifi_utils.reset_wifi(self.dut)
+        """ End test make sure the DUT return idle"""
+        for ad in self.android_devices:
+            wifi_utils.reset_wifi(ad)
+        tele_utils.ensure_phones_idle(self.log, self.android_devices)
 
 
     """Helper Functions"""
@@ -170,6 +184,48 @@
         two_phone_call_short_seq(self.log, self.ads[0], None, None, self.ads[1],
                                  None, None)
 
+    def _phone_idle_iwlan(self):
+        return phone_idle_iwlan(self.log, self.android_devices[0])
+
+    def _wfc_phone_setup_apm_wifi_preferred(self):
+        return self._wfc_phone_setup(True, WFC_MODE_WIFI_PREFERRED)
+
+    def _wfc_phone_setup(self, is_airplane_mode, wfc_mode, volte_mode=True):
+        """Enables WiFi calling by turning off Airplane Mode and setting up volte
+
+        Args:
+          is_airplane_mode: boolean, True/False to turn on/off Airplane Mode.
+          wfc_mode: str, String stating what WFC Mode is used.
+          volte_mode: boolean, True/False to turn on/off VoLTE Mode.
+
+        Returns:
+          False, when 4G fails or wrong wfc_mode or WiFi does not connect,
+          (failure is logged) True otherwise.
+
+        """
+        tele_utils.toggle_airplane_mode(self.log, self.android_devices[0], False)
+        tele_utils.toggle_volte(self.log, self.android_devices[0], volte_mode)
+        if not tele_utils.ensure_network_generation(
+                self.log,
+                self.android_devices[0],
+                GEN_4G,
+                voice_or_data=NETWORK_SERVICE_DATA):
+            return False
+        if not tele_utils.set_wfc_mode(self.log, self.android_devices[0], wfc_mode):
+            self.log.error("{} set WFC mode failed.".format(
+                self.android_devices[0].serial))
+            return False
+        tele_utils.toggle_airplane_mode(self.log, self.android_devices[0],
+                             is_airplane_mode)
+        if not tele_utils.ensure_wifi_connected(self.log, self.android_devices[0],
+                                     self.wifi_network_ssid,
+                                     self.wifi_network_pass):
+            self.log.error("{} connect WiFI failed".format(
+                self.android_devices[0].serial))
+            return False
+        return True
+
+
     """Tests"""
 
 
@@ -308,3 +364,93 @@
         self.stress_toggle_airplane_and_wifi(self.stress_count)
         self.validate_cellular_and_wifi()
         return True
+
+    @test_tracker_info(uuid="7cd9698c-7cde-4c99-b73a-67a2246ca4ec")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_toggle_WFC_call(self):
+
+        """Test to toggle WiFi and then perform WiFi connection and
+           cellular calls.
+
+        Raises:
+          signals.TestFailure:The Wifi calling test is failed.
+
+        Steps:
+            1. Attach device to voice subscription network.
+            2. Connect to a WiFi network.
+            3. Turn on airplane mode
+            4. Toggle WiFi OFF and ON.
+            5. Make WiFi calling
+            6. Verify device is in WiFi calling
+            5. Hang up the call
+
+        Verification:
+            The device is using WiFi calling to call out.
+
+        """
+        mo_mt=[]
+        if mo_mt == DIRECTION_MOBILE_ORIGINATED:
+            ad_caller = self.ads[0]
+            ad_callee = self.ads[1]
+        else:
+            ad_caller = self.ads[1]
+            ad_callee = self.ads[0]
+        caller_number = tele_utils.get_phone_number(self.log, ad_caller)
+        callee_number = tele_utils.get_phone_number(self.log, ad_callee)
+        self._wfc_phone_setup_apm_wifi_preferred()
+
+        self.connect_to_wifi(self.dut, self.network)
+        asserts.assert_true(
+            acts.utils.force_airplane_mode(self.dut, True),
+                "Can not turn on airplane mode on: %s" % self.dut.serial)
+        time.sleep(1)
+        self.log.info("Toggling wifi ON")
+        wifi_utils.wifi_toggle_state(self.dut, True)
+        time.sleep(10)
+        tele_utils.initiate_call(self.log, ad_caller, callee_number)
+        tele_utils.wait_and_answer_call(self.log, ad_callee, caller_number)
+        if not self._phone_idle_iwlan():
+            self.log.error("no in wifi calling")
+            raise signals.TestFailure("The Wifi calling test is failed."
+                "WiFi State = %d" %self.dut.droid.wifiCheckState())
+        tele_utils.hangup_call(self.log, self.ads[0])
+
+    @test_tracker_info(uuid="c1f0e0a7-b651-4d6c-a4a5-f946cabf56ef")
+    @TelephonyBaseTest.tel_test_wrap
+    def test_back_to_back_of_the_modem_restart(self):
+
+        """Make sure DUT can connect to AP after modem restart
+
+        Raises:
+          signals.TestFailure: The Wifi connect failed after modem restart.
+
+        From b/171275893:
+          b/170702695 has modem back to back restart,
+          it causes WIFi failure and only reboot device can recover.
+          Currently modem team has this test case but they only check modem status.
+          Therefore, we need to add the same test case and the criteria is to check
+          if WiFi works well after back to back modem restart.
+          For the interval setting between 2 restarts, we suggest to use 20s.
+          We can change to different interval if necessary.
+
+        Steps:
+            1.Restart the modem once
+            2.Waiting for 20s
+            3.Restart the modem again
+            4.Go to Settings ->Network & internet ->Wi-Fi
+            5.To check the DUT can find WiFi AP then connect it
+        Verification:
+            DUT can connect to AP successfully
+            DUT can access the Internet after connect to AP.
+
+        """
+        self.dut = self.android_devices[0]
+        try:
+            tele_utils.trigger_modem_crash_by_modem(self.dut)
+            time.sleep(20)
+            tele_utils.trigger_modem_crash_by_modem(self.dut)
+            time.sleep(20)
+            self.connect_to_wifi(self.dut, self.network)
+        except:
+            raise signals.TestFailure("The Wifi connect failed after modem restart."
+                "WiFi State = %d" %self.dut.droid.wifiCheckState())
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiTetheringTest.py b/acts_tests/tests/google/wifi/WifiTetheringTest.py
index 6612faa..99028d0 100644
--- a/acts_tests/tests/google/wifi/WifiTetheringTest.py
+++ b/acts_tests/tests/google/wifi/WifiTetheringTest.py
@@ -45,7 +45,7 @@
         super().setup_class()
         self.hotspot_device = self.android_devices[0]
         self.tethered_devices = self.android_devices[1:]
-        req_params = ("url", "open_network")
+        req_params = ("url", "open_network", "wifi6_models")
         self.unpack_userparams(req_params)
         self.network = {"SSID": "hotspot_%s" % utils.rand_ascii_str(6),
                         "password": "pass_%s" % utils.rand_ascii_str(6)}
@@ -447,6 +447,9 @@
         wutils.connect_to_wifi_network(self.tethered_devices[0],
                                        self.network,
                                        check_connectivity=True)
+        wutils.verify_11ax_softap(self.hotspot_device,
+                                  self.tethered_devices[0],
+                                  self.wifi6_models)
 
     @test_tracker_info(uuid="17e450f4-795f-4e67-adab-984940dffedc")
     def test_wifi_tethering_wpapsk_network_5g(self):
@@ -460,6 +463,9 @@
         wutils.connect_to_wifi_network(self.tethered_devices[0],
                                        self.network,
                                        check_connectivity=True)
+        wutils.verify_11ax_softap(self.hotspot_device,
+                                  self.tethered_devices[0],
+                                  self.wifi6_models)
 
     @test_tracker_info(uuid="2bc344cb-0277-4f06-b6cc-65b3972086ed")
     def test_change_wifi_hotspot_ssid_when_hotspot_enabled(self):
@@ -482,6 +488,9 @@
                 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)
+        wutils.verify_11ax_softap(self.hotspot_device,
+                                  self.tethered_devices[0],
+                                  self.wifi6_models)
 
         # update the wifi ap configuration with new ssid
         config = {wutils.WifiEnums.SSID_KEY: self.new_ssid}
@@ -498,6 +507,9 @@
                        wutils.WifiEnums.PWD_KEY: \
                        self.network[wutils.WifiEnums.PWD_KEY]}
         wutils.connect_to_wifi_network(self.tethered_devices[0], new_network)
+        wutils.verify_11ax_softap(self.hotspot_device,
+                                  self.tethered_devices[0],
+                                  self.wifi6_models)
 
     @test_tracker_info(uuid="4cf7ab26-ca2d-46f6-9d3f-a935c7e04c97")
     def test_wifi_tethering_open_network_2g(self):
@@ -519,6 +531,9 @@
         wutils.connect_to_wifi_network(self.tethered_devices[0],
                                        open_network,
                                        check_connectivity=True)
+        wutils.verify_11ax_softap(self.hotspot_device,
+                                  self.tethered_devices[0],
+                                  self.wifi6_models)
 
     @test_tracker_info(uuid="f407049b-1324-40ea-a8d1-f90587933310")
     def test_wifi_tethering_open_network_5g(self):
@@ -540,6 +555,9 @@
         wutils.connect_to_wifi_network(self.tethered_devices[0],
                                        open_network,
                                        check_connectivity=True)
+        wutils.verify_11ax_softap(self.hotspot_device,
+                                  self.tethered_devices[0],
+                                  self.wifi6_models)
 
     @test_tracker_info(uuid="d964f2e6-0acb-417c-ada9-eb9fc5a470e4")
     def test_wifi_tethering_open_network_2g_stress(self):
diff --git a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
index 5a63fd6..59c8575 100644
--- a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
+++ b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
@@ -58,7 +58,7 @@
         self.publish_testcase_metrics = True
         # Generate test cases
         self.tests = self.generate_test_cases([6, 36, 149],
-                                              ['VHT20', 'VHT40', 'VHT80'],
+                                              ['bw20', 'bw40', 'bw80'],
                                               ['TCP', 'UDP'], ['DL', 'UL'],
                                               ['high', 'low'])
 
@@ -66,13 +66,15 @@
                             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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
+
         test_cases = []
         for channel, mode, signal_level, traffic_type, traffic_direction in itertools.product(
                 channels,
@@ -81,11 +83,13 @@
                 traffic_types,
                 traffic_directions,
         ):
-            if channel not in allowed_configs[mode]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             testcase_params = collections.OrderedDict(
                 channel=channel,
                 mode=mode,
+                bandwidth=bandwidth,
                 traffic_type=traffic_type,
                 traffic_direction=traffic_direction,
                 signal_level=signal_level)
@@ -123,7 +127,7 @@
         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.")
+                                'Can not turn on airplane mode.')
         wutils.wifi_toggle_state(self.dut, True)
 
     def teardown_test(self):
@@ -400,6 +404,16 @@
             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'
@@ -407,13 +421,17 @@
             testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
                 duration=self.testclass_params['iperf_duration'],
                 reverse_direction=1,
-                traffic_type=testcase_params['traffic_type'])
+                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'])
+                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
@@ -551,18 +569,21 @@
                             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
+            20: [
+                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, 149, 157],
-            'VHT80': [36, 149]
+            40: [36, 44, 100, 149, 157],
+            80: [36, 100, 149],
+            160: [36]
         }
+
         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]:
+            bandwidth = int(''.join([x for x in mode if x.isdigit()]))
+            if channel not in allowed_configs[bandwidth]:
                 continue
             testcase_params = collections.OrderedDict(
                 channel=channel,
@@ -587,7 +608,7 @@
                                                 ):
     def __init__(self, controllers):
         WifiOtaThroughputStabilityTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases([6, 36, 149], ['VHT20', 'VHT80'],
+        self.tests = self.generate_test_cases([6, 36, 149], ['bw20', 'bw80'],
                                               ['TCP'], ['DL', 'UL'],
                                               ['high', 'low'], 'orientation',
                                               list(range(0, 360, 10)))
@@ -596,7 +617,7 @@
 class WifiOtaThroughputStability_45Degree_Test(WifiOtaThroughputStabilityTest):
     def __init__(self, controllers):
         WifiOtaThroughputStabilityTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases([6, 36, 149], ['VHT20', 'VHT80'],
+        self.tests = self.generate_test_cases([6, 36, 149], ['bw20', 'bw80'],
                                               ['TCP'], ['DL', 'UL'],
                                               ['high', 'low'], 'orientation',
                                               list(range(0, 360, 45)))
@@ -606,8 +627,8 @@
         WifiOtaThroughputStabilityTest):
     def __init__(self, controllers):
         WifiOtaThroughputStabilityTest.__init__(self, controllers)
-        self.tests = self.generate_test_cases([6, 36, 149], ['VHT20', 'VHT80'],
+        self.tests = self.generate_test_cases([6, 36, 149], ['bw20', 'bw80'],
                                               ['TCP'], ['DL', 'UL'],
                                               ['high', 'low'],
                                               'stepped stirrers',
-                                              list(range(100)))
+                                              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
index 4a06d5f..99870f6 100644
--- a/acts_tests/tests/google/wifi/WifiWakeTest.py
+++ b/acts_tests/tests/google/wifi/WifiWakeTest.py
@@ -43,6 +43,9 @@
     * 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()
@@ -58,6 +61,9 @@
 
         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"]
@@ -65,34 +71,53 @@
 
         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()
@@ -107,13 +132,10 @@
         self.dut.ed.clear_all_events()
 
     def teardown_test(self):
+        super().teardown_test()
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
 
-    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)
-
     def find_ssid_in_scan_results(self, scan_results_batches, ssid):
         scan_results_batch = scan_results_batches[0]
         scan_results = scan_results_batch["ScanResults"]
@@ -202,7 +224,7 @@
         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.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
 
         self.ap_a_on()
         self.do_location_scan(
@@ -246,7 +268,7 @@
         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.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
 
         self.ap_a_on()
         self.do_location_scan(
@@ -314,7 +336,7 @@
         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.do_location_scan(2 * 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])
@@ -382,7 +404,7 @@
         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.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
 
         self.ap_a_on()
         self.ap_b_on()
diff --git a/acts_tests/tests/google/wifi/WifiWpa2PersonalTest.py b/acts_tests/tests/google/wifi/WifiWpa2PersonalTest.py
new file mode 100644
index 0000000..71c8a18
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiWpa2PersonalTest.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtWifiSecurity
+from acts.test_decorators import test_tracker_info
+from acts import asserts
+
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiWpa2PersonalTest(WifiBaseTest):
+  """ Wi-Fi WPA2 test
+
+      Test Bed Requirement:
+        * One Android device
+        * One OpenWrt Wi-Fi AP.
+  """
+  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]
+    opt_params = ["pixel_models", "cnss_diag_file"]
+    self.unpack_userparams(opt_params)
+
+  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)
+    wutils.reset_wifi(self.dut)
+
+  def teardown_test(self):
+    super().teardown_test()
+    for ad in self.android_devices:
+      ad.droid.wakeLockRelease()
+      ad.droid.goToSleepNow()
+    wutils.reset_wifi(self.dut)
+
+  def start_openwrt(self, channel_2g=None, channel_5g=None):
+    """Enable one OpenWrt to generate a Wi-Fi network.
+
+      Args:
+        channel_2g: Optional; An integer to represent a Wi-Fi 2g channel.
+          The default value is 6 if it's not given.
+        channel_5g: Optional; An integer to represent a Wi-Fi 5g channel.
+          The default value is 36 if it's not given.
+  """
+    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 "OpenWrtAP" in self.user_params:
+      self.openwrt = self.access_points[0]
+      self.configure_openwrt_ap_and_start(wpa_network=True,
+                                          channel_2g=channel_2g,
+                                          channel_5g=channel_5g)
+      self.wpa2_psk_2g = self.wpa_networks[0]["2g"]
+      self.wpa2_psk_5g = self.wpa_networks[0]["5g"]
+
+  def verify_wpa_network_encryption(self, encryption):
+    result = wutils.get_wlan0_link(self.dut)
+    if encryption == 'psk2+ccmp':
+      asserts.assert_true(
+          result['pairwise_cipher'] == 'CCMP' and
+          result['group_cipher'] == 'CCMP' and
+          result['key_mgmt'] == "WPA2-PSK",
+          'DUT does not connect to {} encryption network'.format(encryption))
+    elif encryption == 'psk2+tkip':
+      asserts.assert_true(
+          result['pairwise_cipher'] == 'TKIP' and
+          result['group_cipher'] == 'TKIP' and
+          result['key_mgmt'] == "WPA2-PSK",
+          'DUT does not connect to {} encryption network'.format(encryption))
+    elif encryption == 'psk2+tkip+ccmp':
+      asserts.assert_true(
+          result['pairwise_cipher'] == 'CCMP' and
+          result['group_cipher'] == 'TKIP' and
+          result['key_mgmt'] == "WPA2-PSK",
+          'DUT does not connect to {} encryption network'.format(encryption))
+
+  """ Tests"""
+
+  @test_tracker_info(uuid="d1f984c9-d85f-4b0d-8d64-2e8d6ce74c48")
+  def test_connect_to_wpa2_psk_ccmp_2g(self):
+    """Generate a Wi-Fi network.
+       Change AP's security type to "WPA2" and cipher to "CCMP".
+       Connect to 2g network.
+    """
+    self.start_openwrt()
+    self.openwrt.log.info("Enable WPA2-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA2_PSK_CCMP)
+    wutils.connect_to_wifi_network(self.dut, self.wpa2_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA2_PSK_CCMP)
+
+  @test_tracker_info(uuid="0f9631e8-04a9-4b9c-8225-ab30b4d1173b")
+  def test_connect_to_wpa2_psk_ccmp_5g(self):
+    """Generate a Wi-Fi network.
+       Change AP's security type to "WPA2" and cipher to "CCMP".
+       Connect to 5g network.
+    """
+    self.start_openwrt()
+    self.openwrt.log.info("Enable WPA2-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA2_PSK_CCMP)
+    wutils.connect_to_wifi_network(self.dut, self.wpa2_psk_5g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA2_PSK_CCMP)
+
+  @test_tracker_info(uuid="e6eb3932-10cc-476f-a5d7-936e2631afc1")
+  def test_connect_to_wpa2_psk_tkip_2g(self):
+    """Generate a Wi-Fi network.
+       Change AP's security type to "WPA2" and cipher to "TKIP".
+       Connect to 2g network.
+    """
+    self.start_openwrt()
+    self.openwrt.log.info("Enable WPA2-PSK TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA2_PSK_TKIP)
+    wutils.connect_to_wifi_network(self.dut, self.wpa2_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA2_PSK_TKIP)
+
+  @test_tracker_info(uuid="59ba3cd4-dbc5-44f9-9290-48ae468a51da")
+  def test_connect_to_wpa2_psk_tkip_5g(self):
+    """Generate a Wi-Fi network.
+       Change AP's security type to "WPA2" and cipher to "TKIP".
+       Connect to 5g network.
+    """
+    self.start_openwrt()
+    self.openwrt.log.info("Enable WPA2-PSK TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA2_PSK_TKIP)
+    wutils.connect_to_wifi_network(self.dut, self.wpa2_psk_5g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA2_PSK_TKIP)
+
+  @test_tracker_info(uuid="a06be3db-d653-4549-95f3-87bbeb0db813")
+  def test_connect_to_wpa2_psk_tkip_and_ccmp_2g(self):
+    """Generate a Wi-Fi network.
+       Change AP's security type to "WPA2" and cipher to "CCMP and TKIP".
+       Connect to 2g network.
+    """
+    self.start_openwrt()
+    self.openwrt.log.info("Enable WPA2-PSK CCMP and TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA2_PSK_TKIP_AND_CCMP)
+    wutils.connect_to_wifi_network(self.dut, self.wpa2_psk_2g)
+    self.verify_wpa_network_encryption(
+        OpenWrtWifiSecurity.WPA2_PSK_TKIP_AND_CCMP)
+
+  @test_tracker_info(uuid="ac9b9581-0b32-42b4-8e76-de702c837b86")
+  def test_connect_to_wpa2_psk_tkip_and_ccmp_5g(self):
+    """Generate a Wi-Fi network.
+       Change AP's security type to "WPA2" and cipher to "CCMP and TKIP".
+       Connect to 5g network.
+    """
+    self.start_openwrt()
+    self.openwrt.log.info("Enable WPA2-PSK CCMP and TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA2_PSK_TKIP_AND_CCMP)
+    wutils.connect_to_wifi_network(self.dut, self.wpa2_psk_5g)
+    self.verify_wpa_network_encryption(
+        OpenWrtWifiSecurity.WPA2_PSK_TKIP_AND_CCMP)
diff --git a/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py b/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
index 4c2639e..3b55b6c 100644
--- a/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
+++ b/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
@@ -37,26 +37,26 @@
     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"
+        "wpa3_rsa3072_network", "rsa2048_client_cert", "rsa2048_client_key",
+        "rsa3072_client_cert_expired", "rsa3072_client_cert_corrupted",
+        "rsa3072_client_cert_unsigned", "rsa3072_client_key_unsigned",
     ]
     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)
 
-  def on_fail(self, test_name, begin_time):
-    self.dut.cat_adb_log(test_name, begin_time)
-    self.dut.take_bug_report(test_name, begin_time)
-
   ### Tests ###
 
   @test_tracker_info(uuid="404c6165-6e23-4ec1-bc2c-9dfdd5c7dc87")
@@ -89,4 +89,125 @@
         "identity": self.wpa3_rsa3072_network["identity"],
         "domain_suffix_match": self.wpa3_rsa3072_network["domain"]
     }
-    wutils.connect_to_wifi_network(self.dut, config)
+    # Synology AP is slow in sending out IP address after the connection.
+    # Increasing the wait time to receive IP address to 60s from 15s.
+    wutils.connect_to_wifi_network(self.dut, config, check_connectivity=False)
+    wutils.validate_connection(self.dut, wait_time=60)
+
+  @test_tracker_info(uuid="4779c662-1925-4c26-a4d6-3d729393796e")
+  def test_connect_to_wpa3_enterprise_insecure_rsa_cert(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.rsa2048_client_cert,
+        Ent.PRIVATE_KEY_ID: self.rsa2048_client_key,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_rsa3072_network["identity"],
+        "domain_suffix_match": self.wpa3_rsa3072_network["domain"]
+    }
+    logcat_msg = "E WifiKeyStore: Invalid certificate type for Suite-B"
+    try:
+      wutils.connect_to_wifi_network(self.dut, config)
+      asserts.fail("WPA3 Ent worked with insecure RSA key. Expected to fail.")
+    except:
+      logcat_search = self.dut.search_logcat(logcat_msg)
+      self.log.info("Logcat search results: %s" % logcat_search)
+      asserts.assert_true(logcat_search, "No valid error msg in logcat")
+
+  @test_tracker_info(uuid="897957f3-de25-4f9e-b6fc-9d7798ea1e6f")
+  def test_connect_to_wpa3_enterprise_expired_rsa_cert(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_expired,
+        Ent.PRIVATE_KEY_ID: self.rsa2048_client_key,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_rsa3072_network["identity"],
+        "domain_suffix_match": self.wpa3_rsa3072_network["domain"]
+    }
+    logcat_msg = "E WifiKeyStore: Invalid certificate type for Suite-B"
+    try:
+      wutils.connect_to_wifi_network(self.dut, config)
+      asserts.fail("WPA3 Ent worked with expired cert. Expected to fail.")
+    except:
+      logcat_search = self.dut.search_logcat(logcat_msg)
+      self.log.info("Logcat search results: %s" % logcat_search)
+      asserts.assert_true(logcat_search, "No valid error msg in logcat")
+
+  @test_tracker_info(uuid="f7ab30e2-f2b5-488a-8667-e45920fc24d1")
+  def test_connect_to_wpa3_enterprise_corrupted_rsa_cert(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_corrupted,
+        Ent.PRIVATE_KEY_ID: self.rsa2048_client_key,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_rsa3072_network["identity"],
+        "domain_suffix_match": self.wpa3_rsa3072_network["domain"]
+    }
+    try:
+      wutils.connect_to_wifi_network(self.dut, config)
+      asserts.fail("WPA3 Ent worked with corrupted cert. Expected to fail.")
+    except:
+      asserts.explicit_pass("Connection failed as expected.")
+
+  @test_tracker_info(uuid="f934f388-dc0b-4c78-a493-026b798c15ca")
+  def test_connect_to_wpa3_enterprise_unsigned_rsa_cert(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_unsigned,
+        Ent.PRIVATE_KEY_ID: self.rsa3072_client_key_unsigned,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_rsa3072_network["identity"],
+        "domain_suffix_match": self.wpa3_rsa3072_network["domain"]
+    }
+    try:
+      wutils.connect_to_wifi_network(self.dut, config)
+      asserts.fail("WPA3 Ent worked with unsigned cert. Expected to fail.")
+    except:
+      asserts.explicit_pass("Connection failed as expected.")
+
+  @test_tracker_info(uuid="7082dc90-5eb8-4055-8b48-b555a98a837a")
+  def test_connect_to_wpa3_enterprise_wrong_domain_rsa_cert(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"]+"_wrong"
+    }
+    try:
+      wutils.connect_to_wifi_network(self.dut, config)
+      asserts.fail("WPA3 Ent worked with unsigned cert. Expected to fail.")
+    except:
+      asserts.explicit_pass("Connection failed as expected.")
+
+  @test_tracker_info(uuid="9ad5fd82-f115-42c3-b8e8-520144485ea1")
+  def test_network_selection_status_for_wpa3_ent_wrong_domain_rsa_cert(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.rsa2048_client_key,
+        WifiEnums.SECURITY: WPA3_SECURITY,
+        "identity": self.wpa3_rsa3072_network["identity"],
+        "domain_suffix_match": self.wpa3_rsa3072_network["domain"]+"_wrong"
+    }
+    try:
+      wutils.connect_to_wifi_network(self.dut, config)
+      asserts.fail("WPA3 Ent worked with corrupted cert. Expected to fail.")
+    except:
+      asserts.assert_true(
+          self.dut.droid.wifiIsNetworkTemporaryDisabledForNetwork(config),
+          "WiFi network is not temporary disabled.")
+      asserts.explicit_pass(
+          "Connection failed with correct network selection status.")
diff --git a/acts_tests/tests/google/wifi/WifiWpa3OweTest.py b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
index d84d13c..c68b3aa 100644
--- a/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
+++ b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
@@ -35,13 +35,10 @@
         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 = ["owe_networks", "sae_networks"]
-        self.unpack_userparams(req_param_names=req_params,)
-        wutils.wifi_toggle_state(self.dut, True)
-        wutils.wifi_toggle_state(self.dut_client, True)
+        opt_params = ["owe_networks", "sae_networks"]
+        req_params = ["wpa3_sae_gcmp_128", "wpa3_sae_gcmp_256", "wifi6_models"]
+        self.unpack_userparams(opt_param_names=opt_params,
+                               req_param_names=req_params)
         if "OpenWrtAP" in self.user_params:
             self.configure_openwrt_ap_and_start(owe_network=True,
                                                 sae_network=True)
@@ -63,7 +60,6 @@
             ad.droid.wakeLockRelease()
             ad.droid.goToSleepNow()
         wutils.reset_wifi(self.dut)
-        wutils.reset_wifi(self.dut_client)
 
     ### Test cases ###
 
@@ -78,10 +74,14 @@
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @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)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
 
     @test_tracker_info(uuid="a8fb46be-3487-4dc8-a393-5af992b27f45")
     def test_connect_to_wpa3_personal_reconnection(self):
@@ -95,5 +95,19 @@
                Second connect request from framework succeeds.
         """
         wutils.connect_to_wifi_network(self.dut, self.wpa3_personal_2g)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
         wutils.toggle_wifi_off_and_on(self.dut)
         wutils.connect_to_wifi_network(self.dut, self.wpa3_personal_2g)
+        wutils.verify_11ax_wifi_connection(
+            self.dut, self.wifi6_models, "wifi6_ap" in self.user_params)
+
+    @test_tracker_info(uuid="b1502202-10c3-4834-a899-5023947fb21d")
+    def test_connect_to_wpa3_personal_gcmp_128(self):
+        """Test connect to WPA3 SAE GCMP 128."""
+        wutils.connect_to_wifi_network(self.dut, self.wpa3_sae_gcmp_128)
+
+    @test_tracker_info(uuid="4d8c3c63-75bf-4131-bb07-fe3f6020389c")
+    def test_connect_to_wpa3_personal_gcmp_256(self):
+        """Test connect to WPA3 SAE GCMP 256."""
+        wutils.connect_to_wifi_network(self.dut, self.wpa3_sae_gcmp_256)
diff --git a/acts_tests/tests/google/wifi/WifiWpaPersonalTest.py b/acts_tests/tests/google/wifi/WifiWpaPersonalTest.py
new file mode 100644
index 0000000..ad8856a
--- /dev/null
+++ b/acts_tests/tests/google/wifi/WifiWpaPersonalTest.py
@@ -0,0 +1,347 @@
+#!/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
+
+from acts import asserts
+from acts import utils
+from acts import signals
+from acts.controllers.openwrt_lib.openwrt_constants import OpenWrtWifiSecurity
+from acts.test_decorators import test_tracker_info
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+WifiEnums = wutils.WifiEnums
+
+
+class WifiWpaPersonalTest(WifiBaseTest):
+  """Test for WPA Personal.
+
+  Test Bed Requirement:
+    * One Android device without sim card.
+    * One OpenWrt Wi-Fi AP.
+  """
+
+  def setup_class(self):
+    super().setup_class()
+    self.dut = self.android_devices[0]
+
+    if "OpenWrtAP" in self.user_params:
+      self.openwrt = self.access_points[0]
+      self.configure_openwrt_ap_and_start(wpa1_network=True)
+
+    req_params = ["OpenWrtAP", "roaming_attn"]
+    opt_params = ["pixel_models", "cnss_diag_file"]
+    self.unpack_userparams(req_params, opt_params)
+    self.wpa_psk_2g = self.wpa1_networks[0]["2g"]
+    self.wpa_psk_5g = self.wpa1_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)
+    utils.force_airplane_mode(self.dut, False)
+
+  def teardown_class(self):
+    super().teardown_class()
+
+  def on_fail(self, test_name, begin_time):
+    super().on_fail(test_name, begin_time)
+    self.dut.cat_adb_log(test_name, begin_time)
+    self.dut.take_bug_report(test_name, begin_time)
+
+  def verify_wpa_network_encryption(self, encryption):
+    result = wutils.get_wlan0_link(self.dut)
+    if encryption == "psk+ccmp":
+      asserts.assert_true(
+          result["pairwise_cipher"] == "CCMP" and
+          result["group_cipher"] == "CCMP",
+          "DUT does not connect to {} encryption network".format(encryption))
+    elif encryption == "psk+tkip":
+      asserts.assert_true(
+          result["pairwise_cipher"] == "TKIP" and
+          result["group_cipher"] == "TKIP",
+          "DUT does not connect to {} encryption network".format(encryption))
+    elif encryption == "psk+tkip+ccmp":
+      asserts.assert_true(
+          result["pairwise_cipher"] == "CCMP" and
+          result["group_cipher"] == "TKIP",
+          "DUT does not connect to {} encryption network".format(encryption))
+
+  ### Tests ###
+
+  @test_tracker_info(uuid="0c68a772-b70c-47d6-88ab-1b069c1d8005")
+  def test_connect_to_wpa_psk_ccmp_2g(self):
+    """Test connection between DUT and WPA PSK CCMP 2G.
+
+    Steps:
+      Change AP's security type to "WPA" and cipher to "CCMP".
+      Connect to 2g network.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+
+  @test_tracker_info(uuid="4722dffc-2960-4459-9729-0f8114af2321")
+  def test_connect_to_wpa_psk_ccmp_5g(self):
+    """Test connection between DUT and WPA PSK CCMP 5G.
+
+    Steps:
+      Change AP's security type to "WPA" and cipher to "CCMP".
+      Connect to 5g network.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_5g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+
+  @test_tracker_info(uuid="4759503e-ef9c-430b-9306-b96a347ca3de")
+  def test_connect_to_wpa_psk_tkip_2g(self):
+    """Test connection between DUT and WPA PSK TKIP 2G.
+
+    Steps:
+      Change AP's security type to "WPA" and cipher to "TKIP".
+      Connect to 2g network.
+    """
+    self.openwrt.log.info("Enable WPA-TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_TKIP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_TKIP)
+
+  @test_tracker_info(uuid="9c836ca6-af14-4d6b-a98e-227fb29e84ee")
+  def test_connect_to_wpa_psk_tkip_5g(self):
+    """Test connection between DUT and WPA PSK TKIP 5G.
+
+    Steps:
+      Change AP's security type to "WPA" and cipher to "TKIP".
+      Connect to 5g network.
+    """
+    self.openwrt.log.info("Enable WPA-PSK TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_TKIP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_5g)
+
+  @test_tracker_info(uuid="c03b362b-cd03-4e34-a99a-ef80a9db6db9")
+  def test_connect_to_wpa_psk_tkip_and_ccmp_2g(self):
+    """Test connection between DUT and WPA PSK CCMP+TKIP 2G.
+
+    Steps:
+      Change AP's security type to "WPA" and cipher to "CCMP and TKIP".
+      Connect to 2g network.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP and TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_TKIP_AND_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(
+        OpenWrtWifiSecurity.WPA_PSK_TKIP_AND_CCMP)
+
+  @test_tracker_info(uuid="203d7e7f-536d-4feb-9aa2-648f1f9a685d")
+  def test_connect_to_wpa_psk_tkip_and_ccmp_5g(self):
+    """Test connection between DUT and WPA PSK CCMP+TKIP 5G.
+
+    Steps:
+        Change AP's security type to "WPA" and cipher to "CCMP and TKIP".
+        Connect to 5g network.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP and TKIP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_TKIP_AND_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_5g)
+    self.verify_wpa_network_encryption(
+        OpenWrtWifiSecurity.WPA_PSK_TKIP_AND_CCMP)
+
+  @test_tracker_info(uuid="20a41f61-4fda-4fe9-82ee-482ecd8c82eb")
+  def test_connect_to_wpa_psk_ccmp_2g_after_airplane_mode(self):
+    """Test Wi-Fi reconnection after enabling Airplane Mode.
+
+    Steps:
+        DUT connect to 2GHz Wi-Fi network.
+        DUT turns ON Airplane Mode.
+        DUT turns ON Wi-Fi.
+        DUT verify internet connection with HTTP ping.
+        DUT turns OFF Airplane Mode.
+        DUT verify internet connection with HTTP ping.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    # Turn ON DUT"s Airplane Mode.
+    self.dut.log.info("Toggle Airplane Mode ON")
+    utils.force_airplane_mode(self.dut, True)
+    self.dut.log.info("Toggle Wi-Fi ON")
+    # Turn ON DUT"s Wi-Fi
+    wutils.wifi_toggle_state(self.dut, True)
+    wutils.wait_for_connect(self.dut,
+                            self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.validate_connection(self.dut)
+    utils.force_airplane_mode(self.dut, False)
+    wutils.validate_connection(self.dut)
+
+  @test_tracker_info(uuid="df89c92b-a30c-4485-ab45-daef5240c027")
+  def test_connect_to_wpa_psk_ccmp_2g_after_wifi_off(self):
+    """Test Wi-Fi reconnection after Turn OFF Wi-Fi.
+
+    Steps:
+        DUT connect to 2GHz Wi-Fi network.
+        DUT turns OFF Wi-Fi.
+        DUT turns ON Wi-Fi.
+        DUT verify internet connection with HTTP ping.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    self.dut.log.info("Toggle Wi-Fi OFF")
+    # Turn OFF DUT"s Wi-Fi then Turn if ON.
+    wutils.wifi_toggle_state(self.dut, False)
+    wutils.wifi_toggle_state(self.dut, True)
+    wutils.wait_for_connect(self.dut,
+                            self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.validate_connection(self.dut)
+
+  @test_tracker_info(uuid="c591e687-340c-42e6-8d85-58a1f930b6b1")
+  def test_connect_to_wpa_psk_ccmp_2g_after_suspend_resume(self):
+    """Test Wi-Fi reconnection after Suspend.
+
+    Steps:
+        DUT connect to 2GHz Wi-Fi network.
+        DUT suspend and resume.
+        DUT verify internet connection with HTTP ping.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    self.dut.log.info("Suspend the DUT and wait for 10 seconds")
+    # Suspend and Resume the DUT.
+    self.dut.go_to_sleep()
+    time.sleep(10)
+    self.dut.log.info("Resume the DUT")
+    self.dut.wakeup_screen()
+    wutils.validate_connection(self.dut)
+
+  @test_tracker_info(uuid="d3e34869-f2ae-4614-983d-19be238d8499")
+  def test_connect_to_wpa_psk_ccmp_2g_after_reboot(self):
+    """Test Wi-Fi reconnection after reboot.
+
+    Steps:
+        DUT connect to 2GHz Wi-Fi network.
+        DUT reboot.
+        DUT verify internet connection with HTTP ping.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    # Reboot the DUT.
+    self.dut.log.info("Reboot the DUT")
+    self.dut.reboot()
+    self.dut.wait_for_boot_completion()
+    wutils.wait_for_connect(self.dut,
+                            self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.validate_connection(self.dut)
+
+  @test_tracker_info(uuid="ebd8dd7f-dc36-4e99-b18c-5f725a2f88b2")
+  def test_connect_to_wpa_psk_ccmp_2g_after_incorrect_password(self):
+    """Test Wi-Fi reconnection after incorrect password.
+
+    Steps:
+        DUT connect to 2GHz Wi-Fi network.
+        DUT try to connect to the Wi-Fi network with incorrect password.
+        Connection fail as expected.
+        DUT connect to the Wi-Fi network with correct password.
+        DUT verify internet connection with HTTP ping.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    self.wpa_psk_2g_fail = self.wpa_psk_2g.copy()
+    self.wpa_psk_2g_fail["password"] = "incorrect_password"
+    # Try to connect a Wi-Fi network with incorrect passwlrd.
+    try:
+      self.dut.log.info("Connect to Wi-Fi with wrong password")
+      wutils.wifi_connect(self.dut, self.wpa_psk_2g_fail, num_of_tries=1)
+    except:
+      self.dut.log.info("Connect to Wi-Fi with correct password")
+      wutils.wifi_connect(self.dut, self.wpa_psk_2g)
+    else:
+      raise signals.TestFailure("DUT connect to Wi-Fi with wrong password")
+    self.verify_wpa_network_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.validate_connection(self.dut)
+
+  @test_tracker_info(uuid="d20fc634-8dcc-4336-9640-2a6907ca1894")
+  def test_connect_to_wpa_psk_ccmp_2g_after_out_of_range(self):
+    """Test Wi-Fi reconnection after out of range.
+
+    Steps:
+        DUT connect to 2GHz Wi-Fi network.
+        DUT out of Wi-Fi range.
+        Make Wi-Fi network is not visible by DUT.
+        DUT back in Wi-Fi range.
+        Wi-Fi network is visible by DUT.
+        DUT connect to the Wi-Fi network.
+        DUT verify internet connection with HTTP ping.
+    """
+    self.openwrt.log.info("Enable WPA-PSK CCMP on OpenWrt AP")
+    self.openwrt.set_wpa_encryption(OpenWrtWifiSecurity.WPA_PSK_CCMP)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
+    # Make the DUT out of range.
+    wutils.set_attns(self.attenuators,
+                     "atten1_off_atten2_off",
+                     self.roaming_attn)
+    wutils.start_wifi_connection_scan_and_ensure_network_not_found(
+        self.dut,
+        self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    # Make the DUT back in range.
+    wutils.set_attns(self.attenuators,
+                     "atten1_on_atten2_on",
+                     self.roaming_attn)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(
+        self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
+    wutils.connect_to_wifi_network(self.dut, self.wpa_psk_2g)
diff --git a/acts_tests/tests/google/wifi/aware/README.md b/acts_tests/tests/google/wifi/aware/README.md
index 11a8296..6b96914 100644
--- a/acts_tests/tests/google/wifi/aware/README.md
+++ b/acts_tests/tests/google/wifi/aware/README.md
@@ -33,7 +33,7 @@
 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/google/wifi/aware/functional/functional`
+`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
diff --git a/acts_tests/tests/google/wifi/aware/config/wifi_aware.json b/acts_tests/tests/google/wifi/aware/config/wifi_aware.json
index 97323fc..8f1252e 100644
--- a/acts_tests/tests/google/wifi/aware/config/wifi_aware.json
+++ b/acts_tests/tests/google/wifi/aware/config/wifi_aware.json
@@ -9,7 +9,8 @@
         }
     ],
     "logpath": "~/logs",
-    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+    "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
index f2e6e79..23dd1bb 100644
--- 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
@@ -9,7 +9,8 @@
         }
     ],
     "logpath": "~/logs",
-    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+    "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
index 89a9bdc..79717c7 100644
--- a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
@@ -13,7 +13,7 @@
 #   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
@@ -149,6 +149,11 @@
 
         # enable airplane mode
         utils.force_airplane_mode(dut, True)
+        # APM has a race condition between tear down the NAN Iface and change the Wifi State.
+        try:
+            dut.ed.pop_event(aconsts.BROADCAST_WIFI_AWARE_AVAILABLE, autils.EVENT_TIMEOUT)
+        except queue.Empty:
+            dut.log.info('Wifi State changes before Interface is torn down')
         autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
 
         # wait a few seconds and disable airplane mode
diff --git a/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py b/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
index 145b1a6..892bc2a 100644
--- a/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
@@ -117,7 +117,7 @@
             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")
+                        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)
 
@@ -178,11 +178,11 @@
     """
         (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)
+            ptype,
+            stype,
+            use_peer_id,
+            pub_on_both=pub_on_both,
+            pub_on_both_same=pub_on_both_same)
 
         passphrase = None
         pmk = None
@@ -481,8 +481,8 @@
     """
         (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)
+            aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE,
+            True)
 
         if pub_mismatch:
             peer_id_on_pub = peer_id_on_pub - 1
@@ -1733,61 +1733,6 @@
 
     #######################################
 
-    @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
@@ -1807,12 +1752,12 @@
         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)
+                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)
@@ -1906,9 +1851,8 @@
                 "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)
+         peer_id_on_pub) = self.set_up_discovery(
+            aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE, True)
 
         p_id2, p_mac = autils.attach_with_identity(p_dut)
         s_id2, s_mac = autils.attach_with_identity(s_dut)
@@ -1936,11 +1880,10 @@
             # request in-band network (to completion)
             p_req_key = self.request_network(
                 p_dut,
-                p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, None))
+                p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub))
             s_req_key = self.request_network(
                 s_dut,
-                s_dut.droid.wifiAwareCreateNetworkSpecifier(
-                    s_disc_id, peer_id_on_sub))
+                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(
@@ -2019,11 +1962,10 @@
             # request in-band network (to completion)
             p_req_key = self.request_network(
                 p_dut,
-                p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, None))
+                p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub))
             s_req_key = self.request_network(
                 s_dut,
-                s_dut.droid.wifiAwareCreateNetworkSpecifier(
-                    s_disc_id, peer_id_on_sub))
+                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(
@@ -2092,10 +2034,10 @@
                 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,
+            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,
+            if inits_on_same_dut else resp_ipv6,
                                  "NDP IPv6 don't match on Sub/other")
         else:
             asserts.assert_false(
@@ -2237,6 +2179,14 @@
             len(self.android_devices) < 3,
             'A minimum of 3 devices is needed to run the test, have %d' % len(
                 self.android_devices))
+        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
+            or self.android_devices[2]
+            .aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES] < 2,
+            "DUTs do not support enough NDIs")
 
         duts = self.android_devices
         loop_len = len(duts)
@@ -2271,8 +2221,8 @@
 
             (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])
+                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)
@@ -2296,3 +2246,110 @@
                 "ifs": ifs,
                 "ipv6s": ipv6s
             })
+
+    @test_tracker_info(uuid="88cd288f-71ad-40fe-94d7-34e60fb6c962")
+    def test_ndp_initiate_from_both_sides_with_accepts_any_responder(self):
+        """Validate when two device both try to initiate a connection to each other, both NDP can be
+        formed.
+
+        1. Device A and B both publish and file accept any request.
+        2. Device A and B both subscribe to the peer
+        3. When get a discovery match file a network request to the peer
+        4. Wait for NDP setup finish.
+        """
+        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.")
+        ptype = aconsts.PUBLISH_TYPE_UNSOLICITED
+        stype = aconsts.SUBSCRIBE_TYPE_PASSIVE
+        dut1 = self.android_devices[0]
+        dut2 = self.android_devices[1]
+
+        # Publisher+Subscriber: attach and wait for confirmation
+        dut1_id = dut1.droid.wifiAwareAttach()
+        autils.wait_for_event(dut1, aconsts.EVENT_CB_ON_ATTACHED)
+        time.sleep(self.device_startup_offset)
+        dut2_id = dut2.droid.wifiAwareAttach()
+        autils.wait_for_event(dut2, aconsts.EVENT_CB_ON_ATTACHED)
+
+        # Publisher: start publish and wait for confirmation
+        dut1_p_disc_id = dut1.droid.wifiAwarePublish(dut1_id, self.create_config(ptype))
+        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        dut2_p_disc_id = dut2.droid.wifiAwarePublish(dut2_id, self.create_config(ptype))
+        autils.wait_for_event(dut2, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+        # Publisher: file an accept any NetworkRequest
+        dut1_p_req_key = self.request_network(
+            dut1,
+            dut1.droid.wifiAwareCreateNetworkSpecifier(
+                dut1_p_disc_id, None, self.PASSPHRASE, None))
+
+        dut2_p_req_key = self.request_network(
+            dut2,
+            dut2.droid.wifiAwareCreateNetworkSpecifier(
+                dut2_p_disc_id, None, self.PASSPHRASE, None))
+
+        # Subscriber: start subscribe and wait for discovery match
+        dut2_s_disc_id = dut2.droid.wifiAwareSubscribe(dut2_id, self.create_config(stype))
+        autils.wait_for_event(dut2, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        dut1_s_disc_id = dut1.droid.wifiAwareSubscribe(dut1_id, self.create_config(stype))
+        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+        discovery_event = autils.wait_for_event(
+            dut2, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        dut2_peer_id_on_sub = discovery_event["data"][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+
+        discovery_event = autils.wait_for_event(
+            dut1, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        dut1_peer_id_on_sub = discovery_event["data"][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Subscriber: file a NetworkRequest with peer Id
+        dut2_s_req_key = self.request_network(
+            dut2,
+            dut2.droid.wifiAwareCreateNetworkSpecifier(
+                dut2_s_disc_id, dut2_peer_id_on_sub, self.PASSPHRASE, None))
+
+        # Avoid publisher and subscriber requests are handled in the same NDP
+        time.sleep(1)
+
+        dut1_s_req_key = self.request_network(
+            dut1,
+            dut1.droid.wifiAwareCreateNetworkSpecifier(
+                dut1_s_disc_id, dut1_peer_id_on_sub, self.PASSPHRASE, None))
+
+        # Publisher+Subscriber: wait and check the network callback.
+        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_p_req_key))
+
+        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_s_req_key))
+
+        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_p_req_key))
+
+        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_s_req_key))
+
diff --git a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
index 6c4e20f..39f009d 100644
--- a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -286,8 +286,11 @@
         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)
+        autils.wait_for_event(p_dut,
+                              aconsts.SESSION_CB_ON_SESSION_TERMINATED)
+        autils.wait_for_event(s_dut,
+                              aconsts.SESSION_CB_ON_SESSION_TERMINATED)
+
 
         # verify that there were no other events
         autils.verify_no_more_events(p_dut, timeout=0)
@@ -543,9 +546,12 @@
         # Publisher+Subscriber: Terminate sessions
         p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
         s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+        autils.wait_for_event(p_dut,
+                              aconsts.SESSION_CB_ON_SESSION_TERMINATED)
+        autils.wait_for_event(s_dut,
+                          aconsts.SESSION_CB_ON_SESSION_TERMINATED)
 
         # 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)
 
@@ -1261,3 +1267,87 @@
         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])
+
+    def run_service_discovery_on_service_lost(self, p_type, s_type):
+        """
+        Validate service lost callback will be receive on subscriber, when publisher stopped publish
+    - p_dut running Publish
+    - s_dut running subscribe
+    - s_dut discover p_dut
+    - p_dut stop publish
+    - s_dut receive service lost callback
+
+    Args:
+      p_type: Publish discovery type
+      s_type: Subscribe discovery type
+    """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "Publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "Subscriber"
+
+        asserts.skip_if(not s_dut.droid.isSdkAtLeastS(),
+                        "R build and below do not have onServiceLost API.")
+
+        # 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,
+            p_type,
+            self.PAYLOAD_SIZE_TYPICAL,
+            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,
+            s_type,
+            self.PAYLOAD_SIZE_TYPICAL,
+            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]
+
+        # Publisher+Subscriber: Terminate sessions
+        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+        time.sleep(10)
+        service_lost_event = autils.wait_for_event(
+            s_dut, aconsts.SESSION_CB_ON_SERVICE_LOST)
+        asserts.assert_equal(peer_id_on_sub,
+                             service_lost_event["data"][aconsts.SESSION_CB_KEY_PEER_ID])
+        asserts.assert_equal(aconsts.REASON_PEER_NOT_VISIBLE,
+                             service_lost_event["data"][aconsts.SESSION_CB_KEY_LOST_REASON])
+
+        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+    @test_tracker_info(uuid="b1894ce3-8692-478b-a96f-db2797e22caa")
+    def test_service_discovery_on_service_lost_unsolicited_passive(self):
+        """
+        Test service discovery lost with unsolicited publish and passive subscribe
+        """
+        self.run_service_discovery_on_service_lost(aconsts.PUBLISH_TYPE_UNSOLICITED,
+                                                   aconsts.SUBSCRIBE_TYPE_PASSIVE)
+
+    @test_tracker_info(uuid="4470d897-223a-4f9f-b21f-4061943137dd")
+    def test_service_discovery_on_service_lost_solicited_active(self):
+        """
+        Test service discovery lost with solicited publish and active subscribe
+        """
+        self.run_service_discovery_on_service_lost(aconsts.PUBLISH_TYPE_SOLICITED,
+                                                   aconsts.SUBSCRIBE_TYPE_ACTIVE)
diff --git a/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py b/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
index 56a6547..1c27ab8 100644
--- a/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
@@ -25,6 +25,8 @@
 from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
 from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
 from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 
 # arbitrary timeout for events
 EVENT_TIMEOUT = 10
@@ -48,13 +50,15 @@
     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.
+    P2P and SoftAp has same priority as Aware, will bring down all aware clients,
+    but Aware will keep available.
 
     Args:
       is_p2p: True for p2p, False for SoftAP
     """
         dut = self.android_devices[0]
+        p_config = autils.create_discovery_config(self.SERVICE_NAME,
+                                                  aconsts.PUBLISH_TYPE_UNSOLICITED)
 
         # start Aware
         id = dut.droid.wifiAwareAttach()
@@ -65,25 +69,37 @@
         # start other service
         if is_p2p:
             dut.droid.wifiP2pInitialize()
+            wp2putils.p2p_create_group(dut)
+            dut.ed.pop_event(p2pconsts.CONNECTED_EVENT,
+                        p2pconsts.DEFAULT_TIMEOUT)
+            time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
         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)
+        # Will not make WiFi Aware unavailable
+        autils.fail_on_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+        # Aware session has already been torn down, publish will fail.
+        dut.droid.wifiAwarePublish(id, p_config)
+        autils.wait_for_event(dut, aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED)
 
-        if is_p2p:
-            # P2P has same priority, aware will be available
-            autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+        # Aware will be available, and try to tear down other service when new service request.
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+        # Try to attach again
+        id = dut.droid.wifiAwareAttach()
+        if is_p2p or dut.droid.isSdkAtLeastS():
+            autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+            dut.droid.wifiAwarePublish(id, p_config)
+            autils.wait_for_event(dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
         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)
+            # SoftAp has higher priority on R device, attach should fail
+            autils.fail_on_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+
 
     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.
+    P2P and SoftAp has same priority as Aware,  Aware can bring it down and start service
 
     Args:
       is_p2p: True for p2p, False for SoftAP
@@ -93,46 +109,26 @@
         # 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. 
+        autils.fail_on_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)")
+        autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+        asserts.assert_true(dut.droid.wifiIsAwareAvailable(), "Aware should be available")
 
         dut.droid.wifiAwareAttach()
-        if is_p2p:
-            # P2P has same priority, Aware attach should success.
+        if is_p2p or dut.droid.isSdkAtLeastS():
             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)
+            # SoftAp has higher priority on R device, attach should fail
+            autils.fail_on_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
@@ -174,34 +170,43 @@
         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 start Publish
-        p_disc_id = dut.droid.wifiAwarePublish(p_id, p_config)
-        autils.wait_for_event(dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
-
-        # dut_ap stop softAp and start Subscribe
+        # dut_ap stop softAp and start publish
         wutils.stop_wifi_tethering(dut_ap)
-        autils.wait_for_event(dut_ap, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+        if not dut_ap.droid.wifiIsAwareAvailable():
+            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.wifiAwareSubscribe(s_id, s_config)
-        autils.wait_for_event(dut_ap, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+        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_ap, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+        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"""
+        """Validate that a running p2p session terminates when Aware is started"""
         self.run_incompat_service_then_aware(is_p2p=True)
 
     @test_tracker_info(uuid="1e7b3a6d-575d-4911-80bb-6fcf1157ee9f")
@@ -211,7 +216,7 @@
 
     @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"""
+        """Validate that a running softAp session terminates when Aware is started"""
         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)
diff --git a/acts_tests/tests/google/wifi/aware/functional/ProtocolsMultiCountryTest.py b/acts_tests/tests/google/wifi/aware/functional/ProtocolsMultiCountryTest.py
deleted file mode 100644
index a35c4a6..0000000
--- a/acts_tests/tests/google/wifi/aware/functional/ProtocolsMultiCountryTest.py
+++ /dev/null
@@ -1,223 +0,0 @@
-#!/usr/bin/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_contrib.test_utils.wifi.wifi_test_utils as wutils
-
-import time
-import random
-import re
-import logging
-import acts.controllers.packet_capture as packet_capture
-
-from acts import asserts
-from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.net import nsd_const as nconsts
-from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
-from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
-from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
-from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
-from acts.controllers.ap_lib.hostapd_constants import CHANNEL_MAP
-
-WifiEnums = wutils.WifiEnums
-
-class ProtocolsMultiCountryTest(AwareBaseTest):
-    def __init__(self, controllers):
-        AwareBaseTest.__init__(self, controllers)
-        self.basetest_name = (
-            "ping6_ib_unsolicited_passive_multicountry",
-            "ping6_ib_solicited_active_multicountry",
-            )
-
-        self.generate_tests()
-
-    def generate_testcase(self, basetest_name, country):
-        """Generates a single test case from the given data.
-
-        Args:
-            basetest_name: The name of the base test case.
-            country: The information about the country code under test.
-        """
-        base_test = getattr(self, basetest_name)
-        test_tracker_uuid = ""
-
-        testcase_name = 'test_%s_%s' % (basetest_name, country)
-        test_case = test_tracker_info(uuid=test_tracker_uuid)(
-            lambda: base_test(country))
-        setattr(self, testcase_name, test_case)
-        self.tests.append(testcase_name)
-
-    def generate_tests(self):
-        for country in self.user_params['wifi_country_code']:
-                for basetest_name in self.basetest_name:
-                    self.generate_testcase(basetest_name, country)
-
-    def setup_class(self):
-        super().setup_class()
-        for ad in self.android_devices:
-            ad.droid.wakeLockAcquireBright()
-            ad.droid.wakeUpNow()
-            wutils.wifi_test_device_init(ad)
-
-        if hasattr(self, 'packet_capture'):
-            self.packet_capture = self.packet_capture[0]
-        self.channel_list_2g = WifiEnums.ALL_2G_FREQUENCIES
-        self.channel_list_5g = WifiEnums.ALL_5G_FREQUENCIES
-
-    def setup_test(self):
-        super(ProtocolsMultiCountryTest, self).setup_test()
-        for ad in self.android_devices:
-            ad.ed.clear_all_events()
-
-    def test_time(self,begin_time):
-        super(ProtocolsMultiCountryTest, self).setup_test()
-        for ad in self.android_devices:
-            ad.cat_adb_log(begin_time)
-
-    def teardown_test(self):
-        super(ProtocolsMultiCountryTest, self).teardown_test()
-        for ad in self.android_devices:
-            ad.adb.shell("cmd wifiaware reset")
-
- 
-    """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")
-
-    ########################################################################
-
-    def get_ndp_freq(self, dut):
-        """ get aware interface status"""
-        get_nda0 = "timeout 3 logcat | grep getNdpConfirm | grep Channel"
-        out_nda01 = dut.adb.shell(get_nda0)
-        out_nda0 = re.findall("Channel = (\d+)", out_nda01)
-        #out_nda0 = dict(out_nda02[-1:])
-        return out_nda0
-
-
-    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="3b09e666-c526-4879-8180-77d9a55a2833")
-    def ping6_ib_unsolicited_passive_multicountry(self, country):
-        """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]
-        wutils.set_wifi_country_code(p_dut, country)
-        wutils.set_wifi_country_code(s_dut, country)
-        #p_dut.adb.shell("timeout 12 logcat -c")
-        # 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)
-        ndpfreg =int(self.get_ndp_freq(p_dut)[-1])
-        ndp_channel = str(CHANNEL_MAP[ndpfreg])
-        n = int(ndp_channel)
-        if n in range(len(self.channel_list_2g)):
-            ndp_band = '2g'
-        else:
-            ndp_band = '5g'
-        p_dut.log.info('ndp frequency : {}'.format(ndpfreg))
-        p_dut.log.info('ndp channel : {}'.format(ndp_channel))
-        p_dut.log.info('ndp band : {}'.format(ndp_band))
-        if hasattr(self, 'packet_capture'):
-            self.conf_packet_capture(ndp_band, ndpfreg)
-       # 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)
-        if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
-        time.sleep(10)
-
-    @test_tracker_info(uuid="6b54951f-bf0b-4d26-91d6-c9b3b8452873")
-    def ping6_ib_solicited_active_multicountry(self, country):
-        """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]
-        wutils.set_wifi_country_code(p_dut, country)
-        wutils.set_wifi_country_code(s_dut, country)
-
-        # 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)
-        ndpfreg =int(self.get_ndp_freq(p_dut)[-1])
-        ndp_channel = str(CHANNEL_MAP[ndpfreg])
-        n = int(ndp_channel)
-        if n in range(len(self.channel_list_2g)):
-            ndp_band = '2g'
-        else:
-            ndp_band = '5g'
-        p_dut.log.info('ndp frequency : {}'.format(ndpfreg))
-        p_dut.log.info('ndp channel : {}'.format(ndp_channel))
-        p_dut.log.info('ndp band : {}'.format(ndp_band))
-        if hasattr(self, 'packet_capture'):
-            self.conf_packet_capture(ndp_band, ndpfreg)
-        # 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)
-        if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
-        time.sleep(10)
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..2e18bc7
--- /dev/null
+++ b/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
@@ -0,0 +1,485 @@
+#!/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 itertools
+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.controllers.adb_lib.error import AdbCommandError
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from functools import partial
+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']
+        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 setup_test(self):
+        for ad in self.android_devices:
+            wputils.start_wifi_logging(ad)
+
+    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)
+            wputils.stop_wifi_logging(ad)
+
+    def compute_test_metrics(self, rvr_result):
+        #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'])
+
+        test_mode = rvr_result['testcase_params']['mode']
+        tput_below_limit = [
+            tput <
+            self.testclass_params['tput_metric_targets'][test_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'][test_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 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, 'INTERACTIVE')
+            wutils.set_wifi_country_code(ad, self.country_code)
+            try:
+                autils.configure_ndp_allow_any_override(ad, True)
+            except AdbCommandError as e:
+                self.log.warning('AdbCommandError: {}'.format(e))
+            # 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['concurrency_state'][0]:
+            band = testcase_params['concurrency_state'][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['concurrency_state'][1]:
+            if testcase_params['concurrency_state'][0] == testcase_params[
+                    'concurrency_state'][1]:
+                # if connected to same network, add it to the above
+                ap_networks[0]['connected_dut'].append(1)
+            else:
+                band = testcase_params['concurrency_state'][1].split('_')[0]
+                if not testcase_params['concurrency_state'][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)
+
+    def generate_test_cases(self, concurrency_list, traffic_type,
+                            traffic_directions):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+
+        for concurrency_state, traffic_direction in itertools.product(
+                concurrency_list, traffic_directions):
+            connection_string = '_'.join([str(x) for x in concurrency_state
+                                          ]).replace('False', 'disconnected')
+            test_name = 'test_aware_rvr_{}_{}_{}'.format(
+                traffic_type, traffic_direction, connection_string)
+            test_params = collections.OrderedDict(
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction,
+                concurrency_state=concurrency_state)
+            setattr(self, test_name, partial(self._test_aware_rvr,
+                                             test_params))
+            test_cases.append(test_name)
+        return test_cases
+
+
+class WifiAwareRvr_FCC_TCP_Test(WifiAwareRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            ['2G_1', '2G_1'], ['5G_1',
+                                               '5G_1'], ['2G_1', '5G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'US'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='TCP',
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiAwareRvr_FCC_UDP_Test(WifiAwareRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            ['2G_1', '2G_1'], ['5G_1',
+                                               '5G_1'], ['2G_1', '5G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'US'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='UDP',
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiAwareRvr_ETSI_TCP_Test(WifiAwareRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            ['2G_1', '2G_1'], ['5G_1',
+                                               '5G_1'], ['2G_1', '5G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'GB'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='TCP',
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiAwareRvr_ETSI_UDP_Test(WifiAwareRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            ['2G_1', '2G_1'], ['5G_1',
+                                               '5G_1'], ['2G_1', '5G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'GB'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='UDP',
+            traffic_directions=['DL', 'UL'])
diff --git a/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py b/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
index 73e2ffa..01f26cc 100644
--- a/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
+++ b/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
@@ -201,6 +201,8 @@
                     init_req_key)
                 resp_dut.droid.connectivityUnregisterNetworkCallback(
                     resp_req_key)
+                # Wait a minimal amount of time to let device finish the clean-up
+                time.sleep(2)
 
             # clean-up at end of iteration
             init_dut.droid.wifiAwareDestroy(init_id)
diff --git a/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py b/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
index 2112782..85fcd26 100644
--- a/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
+++ b/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
@@ -35,7 +35,7 @@
 
     # Number of iterations in the stress test (number of messages)
     # Should be larger than MESSAGE_QUEUE_DEPTH_PER_UID
-    NUM_ITERATIONS = 100
+    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
diff --git a/acts_tests/tests/google/wifi/example_config_iot.json b/acts_tests/tests/google/wifi/example_config_iot.json
index 42b0be7..133cbe3 100644
--- a/acts_tests/tests/google/wifi/example_config_iot.json
+++ b/acts_tests/tests/google/wifi/example_config_iot.json
@@ -14,7 +14,7 @@
     ],
     "logpath": "/tmp/ACTS_logs",
     "testpaths": [
-        "<path to acts root>/tools/test/connectivity/acts/tests/google/wifi"
+        "<path to acts root>/tools/test/connectivity/acts_tests/tests/google/wifi"
     ],
     "iot_networks": [
         {
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
index 234df4a..6ec0501 100644
--- a/acts_tests/tests/google/wifi/example_connectivity_performance_ap_sta.json
+++ b/acts_tests/tests/google/wifi/example_connectivity_performance_ap_sta.json
@@ -85,5 +85,5 @@
 			 "rtt_std_deviation_threshold": 5
     },
     "logpath": "<path to logs>",
-    "testpaths": ["<path to ACTS root folder>/tools/test/connectivity/acts/tests/google/wifi"]
+    "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
index 31af531..9a1a3ad 100644
--- a/acts_tests/tests/google/wifi/p2p/config/wifi_p2p.json
+++ b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p.json
@@ -10,6 +10,6 @@
     ],
     "skip_read_factory_mac": 1,
     "logpath": "~/logs",
-    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi/p2p"],
+    "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_all.json b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_all.json
new file mode 100644
index 0000000..392a392
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_all.json
@@ -0,0 +1,18 @@
+{
+    "_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,
+    "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/config/wifi_p2p_group.json b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_group.json
index 5ca412d..2e03d52 100644
--- a/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_group.json
+++ b/acts_tests/tests/google/wifi/p2p/config/wifi_p2p_group.json
@@ -13,6 +13,6 @@
     "passphrase": "P2pWorld1234",
     "group_band": "2",
     "logpath": "~/logs",
-    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi/p2p"],
+    "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/WifiP2pMultiCoutryTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiCoutryTest.py
index 00419df..55df50a 100644
--- a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiCoutryTest.py
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiCoutryTest.py
@@ -20,7 +20,7 @@
 import acts.controllers.packet_capture as packet_capture
 import re
 import logging
-
+#import acts.signals as signals
 from acts import asserts
 from acts import utils
 
@@ -30,6 +30,7 @@
 from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 from acts.controllers.ap_lib.hostapd_constants import CHANNEL_MAP
 
+
 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
@@ -46,8 +47,8 @@
     def __init__(self, controllers):
         WifiP2pBaseTest.__init__(self, controllers)
         self.basetest_name = (
-            "test_p2p_connect_via_pbc_and_ping_and_reconnect_multicountry",
             "test_p2p_connect_via_display_and_ping_and_reconnect_multicountry",
+            "test_p2p_connect_via_pbc_and_ping_and_reconnect_multicountry",
             )
 
         self.generate_tests()
@@ -100,8 +101,6 @@
         out_p2p0 = re.findall("freq=(\d+)", out_p2p01)
         return out_p2p0
 
-
-
     """Test Cases"""
 
     @test_tracker_info(uuid="f7d98b9e-494e-4e60-ae29-8418e270d2d8")
@@ -169,6 +168,7 @@
         wp2putils.check_disconnect(
             go_dut, timeout=p2pconsts.DEFAULT_GROUP_CLIENT_LOST_TIME)
         time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
         if hasattr(self, 'packet_capture'):
             wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
         time.sleep(10)
@@ -192,6 +192,7 @@
         wutils.set_wifi_country_code(self.dut1, country)
         wutils.set_wifi_country_code(self.dut2, country)
         wp2putils.p2p_connect(self.dut1, self.dut2, False, WPS_DISPLAY)
+
         p2pfreg = int(self.get_p2p0_freq(self.dut1)[0])
         p2p_channel = str(CHANNEL_MAP[p2pfreg])
         n = int(p2p_channel)
@@ -240,4 +241,3 @@
         if hasattr(self, 'packet_capture'):
             wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
         time.sleep(10)
-
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..50c9c0e
--- /dev/null
+++ b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
@@ -0,0 +1,555 @@
+#!/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
+from functools import partial
+import itertools
+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_client as ipc
+from acts.controllers import iperf_server as ipf
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.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', 'uuid_list']
+        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)
+        wutils.set_wifi_country_code(ad, self.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:
+            wputils.start_wifi_logging(ad)
+            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()
+            wputils.stop_wifi_logging(ad)
+
+    def compute_test_metrics(self, rvr_result):
+        #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'])
+
+        test_mode = rvr_result['testcase_params']['mode']
+        tput_below_limit = [
+            tput <
+            self.testclass_params['tput_metric_targets'][test_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'][test_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 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)
+            wutils.set_wifi_country_code(ad, self.country_code)
+        # 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['concurrency_state'][0]:
+            band = testcase_params['concurrency_state'][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['concurrency_state'][1]:
+            if testcase_params['concurrency_state'][0] == testcase_params[
+                    'concurrency_state'][1]:
+                # if connected to same network, add it to the above
+                ap_networks[0]['connected_dut'].append(1)
+            else:
+                band = testcase_params['concurrency_state'][1].split('_')[0]
+                if not testcase_params['concurrency_state'][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)
+
+    def generate_test_cases(self, concurrency_list, traffic_type,
+                            traffic_directions):
+        """Function that auto-generates test cases for a test class."""
+        test_cases = []
+
+        for concurrency_state, traffic_direction in itertools.product(
+                concurrency_list, traffic_directions):
+            connection_string = '_'.join([str(x) for x in concurrency_state
+                                          ]).replace('False', 'disconnected')
+            test_name = 'test_p2p_rvr_{}_{}_{}'.format(traffic_type,
+                                                       traffic_direction,
+                                                       connection_string)
+            test_params = collections.OrderedDict(
+                traffic_type=traffic_type,
+                traffic_direction=traffic_direction,
+                concurrency_state=concurrency_state)
+            test_class=self.__class__.__name__
+            if hasattr(self, "uuid_list") and test_name in self.uuid_list[test_class]:
+                test_case = test_tracker_info(uuid=self.uuid_list[test_class][test_name])(partial(self._test_p2p_rvr, test_params))
+            else:
+                test_case = partial(self._test_p2p_rvr, test_params)
+            setattr(self, test_name, test_case)
+            test_cases.append(test_name)
+        return test_cases
+
+
+class WifiP2pRvr_FCC_TCP_Test(WifiP2pRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            [False, '2G_1'], [False, '5G_1'], ['2G_1', '2G_1'],
+                            ['5G_1', '5G_1'], ['2G_1',
+                                               '5G_1'], ['5G_1', '2G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'US'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='TCP',
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiP2pRvr_FCC_UDP_Test(WifiP2pRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            [False, '2G_1'], [False, '5G_1'], ['2G_1', '2G_1'],
+                            ['5G_1', '5G_1'], ['2G_1',
+                                               '5G_1'], ['5G_1', '2G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'US'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='UDP',
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiP2pRvr_ETSI_TCP_Test(WifiP2pRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            [False, '2G_1'], [False, '5G_1'], ['2G_1', '2G_1'],
+                            ['5G_1', '5G_1'], ['2G_1',
+                                               '5G_1'], ['5G_1', '2G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'GB'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='TCP',
+            traffic_directions=['DL', 'UL'])
+
+
+class WifiP2pRvr_ETSI_UDP_Test(WifiP2pRvrTest):
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        concurrency_list = [[False, False], ['2G_1', False], ['5G_1', False],
+                            [False, '2G_1'], [False, '5G_1'], ['2G_1', '2G_1'],
+                            ['5G_1', '5G_1'], ['2G_1',
+                                               '5G_1'], ['5G_1', '2G_1'],
+                            ['2G_1', '2G_2'], ['5G_1', '5G_2']]
+        self.country_code = 'GB'
+        self.tests = self.generate_test_cases(
+            concurrency_list=concurrency_list,
+            traffic_type='UDP',
+            traffic_directions=['DL', 'UL'])
diff --git a/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json b/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
index 315beac..1870fe9 100644
--- a/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
+++ b/acts_tests/tests/google/wifi/rtt/config/wifi_rtt.json
@@ -9,7 +9,7 @@
         }
     ],
     "logpath": "~/logs",
-    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+    "testpaths": ["./tools/test/connectivity/acts_tests/tests/google/wifi"],
     "adb_logcat_param": "-b all",
     "aware_default_power_mode": "INTERACTIVE",
     "lci_reference": [],
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
index a963779..f722175 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
@@ -22,7 +22,7 @@
 from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
-class RangeApNonSupporting11McTest(WifiBaseTest, RttBaseTest):
+class RangeApNonSupporting11McTest(RttBaseTest, WifiBaseTest):
     """Test class for RTT ranging to Access Points which do not support IEEE
     802.11mc
     """
@@ -111,6 +111,8 @@
         device not having privilege access (expect failures).
         """
         dut = self.android_devices[0]
+        asserts.skip_if(dut.droid.isSdkAtLeastS(),
+                        "Build at least S doesn't need privilege access to use one-sided RTT.")
         rutils.config_privilege_override(dut, True)
         non_rtt_aps = rutils.select_best_scan_results(
             rutils.scan_with_rtt_support_constraint(dut, False),
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
index c48d603..1051fc4 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
@@ -122,9 +122,10 @@
                  autils.create_discovery_config(
                      self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
                  True),
-             s_config=autils.add_ranging_to_pub(
+             s_config=autils.add_ranging_to_sub(
                  autils.create_discovery_config(
-                     self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE), True),
+                     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())
 
@@ -302,8 +303,8 @@
             disable_publish: if true disable ranging on publish config, otherwise disable ranging on
                             subscribe config
         """
-        p_dut = self.android_devices[1]
-        s_dut = self.android_devices[0]
+        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(
@@ -545,12 +546,14 @@
 
     @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.
+        """
+        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.
+        """
+        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/RttDisableTest.py b/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
index 5cf1f25..f3b39a5 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
@@ -30,6 +30,9 @@
     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:
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py
new file mode 100644
index 0000000..7f0f121
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py
@@ -0,0 +1,136 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiEnterpriseTest import WifiEnterpriseTest
+
+WifiEnums = wutils.WifiEnums
+# EAP Macros
+EAP = WifiEnums.Eap
+EapPhase2 = WifiEnums.EapPhase2
+Ent = WifiEnums.Enterprise
+
+
+class WifiEnterprise11axTest(WifiEnterpriseTest):
+  """Tests for WPA2 Enterprise 11ax.
+
+  Test Bed Requirement:
+    One Android device, 1 Asus AXE11000 Access Point and Radius server
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_eap_connect_with_config_tls",
+        "test_eap_connect_with_config_sim",
+        "test_eap_connect_with_config_aka",
+        "test_eap_connect_with_config_aka_prime",
+        "test_eap_connect_with_config_ttls_none",
+        "test_eap_connect_with_config_ttls_pap",
+        "test_eap_connect_with_config_ttls_mschap",
+        "test_eap_connect_with_config_ttls_mschapv2",
+        "test_eap_connect_with_config_ttls_gtc",
+        "test_eap_connect_with_config_peap0_mschapv2",
+        "test_eap_connect_with_config_peap0_gtc",
+        "test_eap_connect_with_config_peap1_mschapv2",
+        "test_eap_connect_with_config_peap1_gtc",
+        "test_eap_connect_config_store_with_config_tls",
+        "test_eap_connect_config_store_with_config_sim",
+        "test_eap_connect_config_store_with_config_aka",
+        "test_eap_connect_config_store_with_config_aka_prime",
+        "test_eap_connect_config_store_with_config_ttls_none",
+        "test_eap_connect_config_store_with_config_ttls_pap",
+        "test_eap_connect_config_store_with_config_ttls_mschap",
+        "test_eap_connect_config_store_with_config_ttls_mschapv2",
+        "test_eap_connect_config_store_with_config_ttls_gtc",
+        "test_eap_connect_config_store_with_config_peap0_mschapv2",
+        "test_eap_connect_config_store_with_config_peap0_gtc",
+        "test_eap_connect_config_store_with_config_peap1_mschapv2",
+        "test_eap_connect_config_store_with_config_peap1_gtc",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+    self.dut = self.android_devices[0]
+    required_userparam_names = [
+        "ca_cert", "client_cert", "client_key", "eap_identity", "eap_password",
+        "device_password", "radius_conf_2g", "radius_conf_5g", "wifi6_models"
+    ]
+    self.unpack_userparams(required_userparam_names, ocsp=0)
+    self.ap = self.access_points[0]
+    self.ap.configure_ap({
+        "2g": {
+            "security": "wpa2",
+            "radius_server_ip": self.radius_conf_2g["radius_server_ip"],
+            "radius_server_port": self.radius_conf_2g["radius_server_port"],
+            "radius_server_secret": self.radius_conf_2g["radius_server_secret"],
+        },
+        "5g": {
+            "security": "wpa2",
+            "radius_server_ip": self.radius_conf_5g["radius_server_ip"],
+            "radius_server_port": self.radius_conf_5g["radius_server_port"],
+            "radius_server_secret": self.radius_conf_5g["radius_server_secret"],
+        }
+    })
+    self.ent_network_2g = self.ap.get_wifi_network("2g")
+    self.ent_network_5g = self.ap.get_wifi_network("5g")
+
+    # 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_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],
+    }
+
+    # Set screen lock password so ConfigStore is unlocked.
+    self.dut.droid.setDevicePassword(self.device_password)
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py
new file mode 100644
index 0000000..ec70da9
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py
@@ -0,0 +1,108 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiEnterpriseRoamingTest import WifiEnterpriseRoamingTest
+
+WifiEnums = wutils.WifiEnums
+# EAP Macros
+EAP = WifiEnums.Eap
+EapPhase2 = WifiEnums.EapPhase2
+Ent = WifiEnums.Enterprise
+
+
+class WifiEnterpriseRoaming11axTest(WifiEnterpriseRoamingTest):
+  """Tests for WPA2 Enterprise 11ax.
+
+  Test Bed Requirement:
+    One Android device, 2 Asus AXE11000 Access Point and Radius server
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_roaming_with_config_tls",
+        "test_roaming_with_config_ttls_none",
+        "test_roaming_with_config_ttls_pap",
+        "test_roaming_with_config_ttls_mschap",
+        "test_roaming_with_config_ttls_mschapv2",
+        "test_roaming_with_config_ttls_gtc",
+        "test_roaming_with_config_peap_mschapv2",
+        "test_roaming_with_config_peap_gtc",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    req_params = [
+        "attn_vals", "roam_interval", "ca_cert", "client_cert", "client_key",
+        "eap_identity", "eap_password", "device_password", "wifi6_models",
+        "bssid_map", "radius_conf_2g", "radius_conf_5g", "roaming_attn"
+    ]
+    self.unpack_userparams(req_params)
+    self.ap1 = self.access_points[0]
+    self.ap2 = self.access_points[1]
+    self.ent_roaming_ssid = "test_ent_roaming"
+    self.ap1.configure_ap({
+        "2g": {
+            "security": "wpa2",
+            "radius_server_ip": self.radius_conf_2g["radius_server_ip"],
+            "radius_server_port": self.radius_conf_2g["radius_server_port"],
+            "radius_server_secret": self.radius_conf_2g["radius_server_secret"],
+            "ssid": self.ent_roaming_ssid,
+        }
+    })
+    self.ap2.configure_ap({
+        "2g": {
+            "security": "wpa2",
+            "radius_server_ip": self.radius_conf_2g["radius_server_ip"],
+            "radius_server_port": self.radius_conf_2g["radius_server_port"],
+            "radius_server_secret": self.radius_conf_2g["radius_server_secret"],
+            "ssid": self.ent_roaming_ssid,
+        }
+    })
+    self.bssid_a = self.bssid_map[0]["2g"]
+    self.bssid_b = self.bssid_map[1]["2g"]
+    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.attn_a = self.attenuators[0]
+    self.attn_b = self.attenuators[2]
+
+    # Set screen lock password so ConfigStore is unlocked.
+    self.dut.droid.setDevicePassword(self.device_password)
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py
new file mode 100644
index 0000000..d2fb981
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py
@@ -0,0 +1,84 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiManagerTest import WifiManagerTest
+
+
+class WifiManager11axTest(WifiManagerTest):
+  """Tests for WifiManager 11ax.
+
+  Test Bed Requirement:
+    One Android device and 2 Asus AXE11000 Access Point.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_toggle_wifi_state_and_get_startupTime",
+        "test_toggle_with_screen",
+        "test_scan",
+        "test_scan_with_wifi_off_and_location_scan_on",
+        "test_scan_after_reboot_with_wifi_off_and_location_scan_on",
+        "test_scan_with_wifi_off_and_location_scan_off",
+        "test_add_network",
+        "test_forget_network",
+        "test_reconnect_to_previously_connected",
+        "test_reconnect_toggle_wifi",
+        "test_reconnect_toggle_wifi_with_location_scan_on",
+        "test_reconnect_toggle_airplane",
+        "test_reconnect_toggle_airplane_with_location_scan_on",
+        "test_reboot_configstore_reconnect",
+        "test_reboot_configstore_reconnect_with_location_scan_on",
+        "test_toggle_wifi_reboot_configstore_reconnect",
+        "test_toggle_wifi_reboot_configstore_reconnect_with_location_scan_on",
+        "test_toggle_airplane_reboot_configstore_reconnect",
+        "test_toggle_airplane_reboot_configstore_reconnect_with_location_scan_on",
+        "test_reboot_configstore_reconnect_with_screen_lock",
+        "test_connect_to_5g_after_reboot_without_unlock",
+        "test_config_store_with_wpapsk_2g",
+        "test_config_store_with_wpapsk_5g",
+        "test_tdls_supported",
+        "test_energy_info",
+        "test_energy_info_connected",
+        "test_connect_to_wpa_2g",
+        "test_connect_to_wpa_5g",
+        "test_connect_to_2g_can_be_pinged",
+        "test_connect_to_5g_can_be_pinged",
+        "test_wifi_saved_network_reset",
+        "test_reboot_wifi_and_bluetooth_on",
+        "test_scan_result_api",
+        "test_enable_disable_auto_join_saved_network",
+        "test_set_get_coex_unsafe_channels"
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    self.dut_client = self.android_devices[1]
+    req_params = [
+        "reference_networks", "wpa_networks", "iperf_server_address",
+        "iperf_server_port", "coex_unsafe_channels", "coex_restrictions",
+        "wifi6_models"
+    ]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap = self.access_points[1]
+    self.ap.configure_ap({"2g": {"security": "open"},
+                          "5g": {"security": "open"}})
+    self.wpapsk_2g = self.reference_networks[0]["2g"]
+    self.wpapsk_5g = self.reference_networks[0]["5g"]
+    self.open_network_2g = self.ap.get_wifi_network("2g")
+    self.open_network_5g = self.ap.get_wifi_network("5g")
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py
new file mode 100644
index 0000000..2c45e8c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py
@@ -0,0 +1,71 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiNetworkSuggestionTest import WifiNetworkSuggestionTest
+
+WifiEnums = wutils.WifiEnums
+# EAP Macros
+EAP = WifiEnums.Eap
+EapPhase2 = WifiEnums.EapPhase2
+Ent = WifiEnums.Enterprise
+
+
+class WifiNetworkSuggestion11axTest(WifiNetworkSuggestionTest):
+  """Tests for Enterprise network suggestion 11ax.
+
+  Test Bed Requirement:
+    One Android device, 1 Asus AXE11000 Access Point and Radius server
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_connect_to_wpa_ent_config_ttls_pap_reboot_config_store",
+        "test_connect_to_wpa_ent_config_aka_reboot_config_store",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    req_params = [
+        "radius_conf_2g", "wifi6_models", "ca_cert", "eap_identity",
+        "eap_password", "domain_suffix_match"
+    ]
+    self.unpack_userparams(req_params)
+    self.ap = self.access_points[0]
+    self.ap.configure_ap({
+        "2g": {
+            "security": "wpa2",
+            "radius_server_ip": self.radius_conf_2g["radius_server_ip"],
+            "radius_server_port": self.radius_conf_2g["radius_server_port"],
+            "radius_server_secret": self.radius_conf_2g["radius_server_secret"],
+        }
+    })
+    self.ent_network_2g = self.ap.get_wifi_network("2g")
+    self.dut.droid.wifiRemoveNetworkSuggestions([])
+
+  def setup_test(self):
+    WifiBaseTest.setup_test(self)
+
+    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()))
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py
new file mode 100644
index 0000000..7509d28
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py
@@ -0,0 +1,58 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiPnoTest import WifiPnoTest
+
+MAX_ATTN = 95
+
+
+class WifiPno11axTest(WifiPnoTest):
+  """Tests for pno for 11ax.
+
+  Test Bed Requirement:
+    1 Android devices, 2 Asus AXE11000 Access Point.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_simple_pno_connection_to_2g",
+        "test_simple_pno_connection_to_5g",
+        "test_pno_connection_with_multiple_saved_networks"
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    req_params = [
+        "attn_vals", "pno_interval", "reference_networks", "wifi6_models"
+    ]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.attn_a = self.attenuators[0]
+    self.attn_b = self.attenuators[2]
+    self.attenuators[1].set_atten(MAX_ATTN)
+    self.attenuators[3].set_atten(MAX_ATTN)
+    time.sleep(self.pno_interval)  # wait for sometime for 5GHz to come up.
+    self.set_attns("default")
+    self.pno_network_a = self.reference_networks[0]["2g"]
+    self.pno_network_b = self.reference_networks[1]["5g"]
+
+  def teardown_class(self):
+    self.attenuators[1].set_atten(0)
+    self.attenuators[3].set_atten(0)
+
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py
new file mode 100644
index 0000000..0155be4
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py
@@ -0,0 +1,120 @@
+#
+#   Copyright 2021 - 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.controllers.ap_lib import hostapd_constants
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiSoftApAcsTest import WifiSoftApAcsTest
+
+
+class WifiSoftApAcs11axTest(WifiSoftApAcsTest):
+  """Tests for Automatic Channel Selection for 11ax."""
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_softap_2G_clean_env",
+        "test_softap_5G_clean_env",
+        "test_softap_auto_clean_env",
+        "test_softap_2G_avoid_channel_6",
+        "test_softap_5G_avoid_channel_6",
+        "test_softap_2G_avoid_channel_36",
+        "test_softap_5G_avoid_channel_36",
+        "test_softap_2G_avoid_channel_1",
+        "test_softap_5G_avoid_channel_1",
+        "test_softap_2G_avoid_channel_2",
+        "test_softap_5G_avoid_channel_2",
+        "test_softap_2G_avoid_channel_3",
+        "test_softap_5G_avoid_channel_3",
+        "test_softap_2G_avoid_channel_4",
+        "test_softap_5G_avoid_channel_4",
+        "test_softap_2G_avoid_channel_5",
+        "test_softap_5G_avoid_channel_5",
+        "test_softap_2G_avoid_channel_7",
+        "test_softap_5G_avoid_channel_7",
+        "test_softap_2G_avoid_channel_8",
+        "test_softap_5G_avoid_channel_8",
+        "test_softap_2G_avoid_channel_9",
+        "test_softap_5G_avoid_channel_9",
+        "test_softap_2G_avoid_channel_10",
+        "test_softap_5G_avoid_channel_10",
+        "test_softap_2G_avoid_channel_11",
+        "test_softap_5G_avoid_channel_11",
+        "test_softap_2G_avoid_channel_40",
+        "test_softap_5G_avoid_channel_40",
+        "test_softap_2G_avoid_channel_44",
+        "test_softap_5G_avoid_channel_44",
+        "test_softap_2G_avoid_channel_48",
+        "test_softap_5G_avoid_channel_48",
+        "test_softap_2G_avoid_channel_149",
+        "test_softap_5G_avoid_channel_149",
+        "test_softap_2G_avoid_channel_153",
+        "test_softap_5G_avoid_channel_153",
+        "test_softap_2G_avoid_channel_157",
+        "test_softap_5G_avoid_channel_157",
+        "test_softap_2G_avoid_channel_161",
+        "test_softap_5G_avoid_channel_161",
+        "test_softap_2G_avoid_channel_165",
+        "test_softap_5G_avoid_channel_165",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    self.dut_client = self.android_devices[1]
+    for ad in self.android_devices:
+      # Enable verbose logging on the duts
+      ad.droid.wifiEnableVerboseLogging(1)
+      asserts.assert_equal(ad.droid.wifiGetVerboseLoggingLevel(), 1,
+                           "Failed to enable WiFi verbose logging.")
+    req_params = ["reference_networks", "wifi6_models",]
+    opt_params = ["iperf_server_address", "iperf_server_port"]
+    self.unpack_userparams(
+        req_param_names=req_params, opt_param_names=opt_params)
+    self.ap = self.access_points[0]
+    self.chan_map = {v: k for k, v in hostapd_constants.CHANNEL_MAP.items()}
+
+  def setup_test(self):
+    WifiBaseTest.setup_test(self)
+    self.dut.droid.wakeLockAcquireBright()
+    self.dut.droid.wakeUpNow()
+
+  def teardown_test(self):
+    WifiBaseTest.teardown_test(self)
+    self.dut.droid.wakeLockRelease()
+    self.dut.droid.goToSleepNow()
+    wutils.stop_wifi_tethering(self.dut)
+    for ad in self.android_devices:
+      wutils.reset_wifi(ad)
+
+  ### Helper Methods ###
+
+  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 int(self.ap.get_configured_channel("5g")) != channel_5g:
+      self.ap.set_channel_and_apply("5g", channel_5g)
+    if int(self.ap.get_configured_channel("2g")) != channel_2g:
+      self.ap.set_channel_and_apply("2g", channel_2g)
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py
new file mode 100644
index 0000000..bdb9dc1
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py
@@ -0,0 +1,94 @@
+#
+#   Copyright 2021 - 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.controllers.ap_lib import hostapd_constants
+import acts.signals as signals
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
+
+
+class WifiStaApConcurrency11axTest(WifiStaApConcurrencyTest):
+  """Tests for STA+AP concurrency 11ax.
+
+  Test Bed Requirement:
+    One Android device, 1 Asus AXE11000 Access Point and Radius server
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_wifi_connection_5G_softap_5G",
+        "test_wifi_connection_5G_softap_2G",
+        "test_wifi_connection_5G_softap_2G_with_location_scan_on",
+        "test_softap_5G_wifi_connection_5G",
+        "test_softap_2G_wifi_connection_5G",
+        "test_wifi_connection_2G_softap_2G",
+        "test_wifi_connection_2G_softap_5G",
+        "test_softap_2G_wifi_connection_2G",
+        "test_wifi_connection_2G_softap_2G_to_softap_5g",
+        "test_softap_5G_wifi_connection_2G",
+        "test_softap_5G_wifi_connection_2G_with_location_scan_on",
+        "test_wifi_connection_5G_softap_2G_to_softap_5g",
+        "test_wifi_connection_5G_DFS_softap_5G",
+        "test_wifi_connection_5G_DFS_softap_2G",
+        "test_softap_5G_wifi_connection_5G_DFS",
+        "test_softap_2G_wifi_connection_5G_DFS",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    self.dut_client = self.android_devices[1]
+    req_params = ["iperf_server_address", "iperf_server_port", "wifi6_models"]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap = self.access_points[0]
+    self.ap.configure_ap({"2g": {"security": "open"},
+                          "5g": {"security": "open"}})
+    self.open_2g = self.ap.get_wifi_network("2g")
+    self.open_5g = self.ap.get_wifi_network("5g")
+
+  def teardown_test(self):
+    WifiBaseTest.teardown_test(self)
+    # 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()
+
+  ### Helper Methods ###
+
+  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 int(self.ap.get_configured_channel("5g")) != channel_5g:
+      self.ap.set_channel_and_apply("5g", channel_5g)
+    if int(self.ap.get_configured_channel("2g")) != channel_2g:
+      self.ap.set_channel_and_apply("2g", channel_2g)
+
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py
new file mode 100644
index 0000000..215a6e6
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py
@@ -0,0 +1,97 @@
+#
+#   Copyright 2021 - 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.utils as utils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiStaConcurrencyNetworkRequestTest import WifiStaConcurrencyNetworkRequestTest
+
+
+class WifiStaConcurrencyNetworkRequest11axTest(
+    WifiStaConcurrencyNetworkRequestTest):
+  """Tests for STA concurrency network request 11ax.
+
+  Test Bed Requirement:
+    One Android device, 2 Asus AXE11000 Access Points.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_connect_to_2g_p2p_while_connected_to_5g_internet",
+        "test_connect_to_2g_internet_while_connected_to_5g_p2p",
+        "test_connect_to_2g_internet_while_connected_to_2g_p2p",
+        "test_connect_to_5g_internet_while_connected_to_5g_p2p",
+        "test_connect_to_5g_dfs_internet_while_connected_to_5g_dfs_p2p",
+        "test_connect_to_2g_internet_while_connected_to_2g_p2p_same_ssid",
+        "test_connect_to_5g_p2p_while_connected_to_5g_internet_same_ssid",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    wutils.wifi_test_device_init(self.dut)
+    req_params = ["sta_concurrency_supported_models", "wifi6_models"]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap1 = self.access_points[0]
+    self.ap2 = self.access_points[1]
+
+  def teardown_test(self):
+    WifiBaseTest.teardown_test(self)
+    self.disconnect_both()
+    self.dut.droid.wakeLockRelease()
+    self.dut.droid.goToSleepNow()
+    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 configure_ap(self,
+                   channel_2g=None,
+                   channel_5g=None,
+                   channel_2g_ap2=None,
+                   channel_5g_ap2=None,
+                   mirror_ap=False,
+                   ap_count=1):
+    """Configure AP based on test case requirements."""
+    if ap_count == 1:
+      self.ap1.set_channel_and_apply("2g", channel_2g)
+      self.ap1.set_channel_and_apply("5g", channel_5g)
+    elif ap_count == 2 and channel_2g_ap2:
+      ssid1 = "test_2g_1"
+      ssid2 = "test_2g_2"
+      if mirror_ap:
+        ssid1 = "test_%s" % utils.rand_ascii_str(4)
+        ssid2 = ssid1
+      self.ap1.set_channel_and_apply("2g", channel_2g)
+      self.ap2.set_channel_and_apply("2g", channel_2g_ap2)
+      self.ap1.configure_ap({"2g": {"ssid": ssid1},})
+      self.ap2.configure_ap({"2g": {"ssid": ssid2},})
+    elif ap_count == 2 and channel_5g_ap2:
+      ssid1 = "test_5g_1"
+      ssid2 = "test_5g_2"
+      if mirror_ap:
+        ssid1 = "test_%s" % utils.rand_ascii_str(4)
+        ssid2 = ssid1
+      self.ap1.set_channel_and_apply("5g", channel_5g)
+      self.ap2.set_channel_and_apply("5g", channel_5g_ap2)
+      self.ap1.configure_ap({"5g": {"ssid": ssid1},})
+      self.ap2.configure_ap({"5g": {"ssid": ssid2},})
+
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py
new file mode 100644
index 0000000..57ab366
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py
@@ -0,0 +1,45 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiWpa3OweTest import WifiWpa3OweTest
+
+
+class WifiWpa311axTest(WifiWpa3OweTest):
+  """Tests for WPA3 11ax.
+
+  Test Bed Requirement:
+    One Android device and 1 Asus AXE11000 Access Point.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_connect_to_wpa3_personal_2g",
+        "test_connect_to_wpa3_personal_5g",
+        "test_connect_to_wpa3_personal_reconnection"
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    req_params = ["wifi6_models",]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap = self.access_points[0]
+    self.ap.configure_ap({"2g": {"security": "sae"},
+                          "5g": {"security": "sae"}})
+    self.wpa3_personal_2g = self.ap.get_wifi_network("2g")
+    self.wpa3_personal_5g = self.ap.get_wifi_network("5g")
diff --git a/acts_tests/tests/google/wifi/wifi6e/Wifi6eMultiChannelTest.py b/acts_tests/tests/google/wifi/wifi6e/Wifi6eMultiChannelTest.py
new file mode 100644
index 0000000..28d9420
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/Wifi6eMultiChannelTest.py
@@ -0,0 +1,116 @@
+#
+#   Copyright 2021 - 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.test_decorators import test_tracker_info
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class Wifi6eMultiChannelTest(WifiBaseTest):
+  """Tests for 6GHz band."""
+
+  def setup_class(self):
+    super().setup_class()
+
+    self.dut = self.android_devices[0]
+    self.ap = self.access_points[0]
+    self.wifi_network = self.ap.get_wifi_network("6g")
+
+  def setup_test(self):
+    super().setup_test()
+    for ad in self.android_devices:
+      ad.droid.wakeLockAcquireBright()
+      ad.droid.wakeUpNow()
+
+  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="74df7ca9-2e50-4f96-94b3-6d776849847f")
+  def test_connect_to_6g_channel_37(self):
+    """Connect to 6GHz WPA3 SAE on channel 37."""
+    self.ap.set_channel_and_apply("6g", 37)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="9ebc2250-f98a-4f72-9d6e-94b4067ddd19")
+  def test_connect_to_6g_channel_53(self):
+    """Connect to 6GHz WPA3 SAE on channel 53."""
+    self.ap.set_channel_and_apply("6g", 53)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="c6aa700b-0216-4693-8584-d95ec243e801")
+  def test_connect_to_6g_channel_69(self):
+    """Connect to 6GHz WPA3 SAE on channel 69."""
+    self.ap.set_channel_and_apply("6g", 69)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="a122066c-ae55-4b8f-9260-240ac078167d")
+  def test_connect_to_6g_channel_85(self):
+    """Connect to 6GHz WPA3 SAE on channel 85."""
+    self.ap.set_channel_and_apply("6g", 85)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="d222163b-ffc4-4e97-a5e3-c55ce7d24ab1")
+  def test_connect_to_6g_channel_101(self):
+    """Connect to 6GHz WPA3 SAE on channel 101."""
+    self.ap.set_channel_and_apply("6g", 101)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="7251b966-7061-415c-9aca-4e7150383dd2")
+  def test_connect_to_6g_channel_117(self):
+    """Connect to 6GHz WPA3 SAE on channel 117."""
+    self.ap.set_channel_and_apply("6g", 117)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="53b1e07e-66df-46b7-b55b-8045daae500f")
+  def test_connect_to_6g_channel_133(self):
+    """Connect to 6GHz WPA3 SAE on channel 133."""
+    self.ap.set_channel_and_apply("6g", 133)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="16a09fcc-3fe1-4035-bb17-0f51b8d2b6f3")
+  def test_connect_to_6g_channel_149(self):
+    """Connect to 6GHz WPA3 SAE on channel 149."""
+    self.ap.set_channel_and_apply("6g", 149)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="6ea66361-86cf-4cf1-ae26-48008914cea1")
+  def test_connect_to_6g_channel_165(self):
+    """Connect to 6GHz WPA3 SAE on channel 165."""
+    self.ap.set_channel_and_apply("6g", 165)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="9e292afc-21fe-4bb7-81a9-f473b2e051bc")
+  def test_connect_to_6g_channel_181(self):
+    """Connect to 6GHz WPA3 SAE on channel 181."""
+    self.ap.set_channel_and_apply("6g", 181)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="a7167923-9ccf-4e4f-a34a-707c61044d97")
+  def test_connect_to_6g_channel_197(self):
+    """Connect to 6GHz WPA3 SAE on channel 197."""
+    self.ap.set_channel_and_apply("6g", 197)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
+
+  @test_tracker_info(uuid="8d5c96f7-515a-4e85-a60d-eb2e7cc4613d")
+  def test_connect_to_6g_channel_213(self):
+    """Connect to 6GHz WPA3 SAE on channel 213."""
+    self.ap.set_channel_and_apply("6g", 213)
+    wutils.connect_to_wifi_network(self.dut, self.wifi_network)
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiHiddenSSID6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiHiddenSSID6eTest.py
new file mode 100644
index 0000000..84d324c
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiHiddenSSID6eTest.py
@@ -0,0 +1,67 @@
+#
+#   Copyright 2021 - 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.test_decorators import test_tracker_info
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class WifiHiddenSSID6eTest(WifiBaseTest):
+  """Tests for hidden 6e tests.
+
+  Test Bed Requirement:
+    1 Android devices, 1 Asus AXE11000 Access Point.
+  """
+
+  def setup_class(self):
+    super().setup_class()
+
+    self.dut = self.android_devices[0]
+    self.ap = self.access_points[0]
+    self.ap.configure_hidden_network_and_apply("6g", True)
+
+  def teardown_class(self):
+    super.teardown_class()
+    self.ap.configure_hidden_network_and_apply("6g", False)
+
+  def setup_test(self):
+    super().setup_test()
+    for ad in self.android_devices:
+      ad.droid.wakeLockAcquireBright()
+      ad.droid.wakeUpNow()
+
+  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="233b1019-2025-4a5b-8c6f-c442c189fe17")
+  def test_connect_to_6g_wpa3_sae_hidden(self):
+    """Connect to 6GHz WPA3 SAE hidden."""
+    self.ap.configure_ap({"6g": {"security": "sae"}})
+    wifi_network = self.ap.get_wifi_network("6g")
+    wifi_network["hiddenSSID"] = True
+    wutils.connect_to_wifi_network(self.dut, wifi_network, hidden=True)
+
+  @test_tracker_info(uuid="303b0d51-5427-4718-9ac1-45b65dbde56f")
+  def test_connect_to_6g_wpa3_owe_hidden(self):
+    """Connect to 6GHz WPA3 OWE hidden."""
+    self.ap.configure_ap({"6g": {"security": "owe"}})
+    wifi_network = self.ap.get_wifi_network("6g")
+    wutils.connect_to_wifi_network(self.dut, wifi_network, hidden=True)
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py
new file mode 100644
index 0000000..4a0ecf3
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py
@@ -0,0 +1,244 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiNetworkSelectorTest import WifiNetworkSelectorTest
+
+# WifiNetworkSelector imposes a 10 seconds gap between two selections
+NETWORK_SELECTION_TIME_GAP = 12
+LVL_ATTN = 15
+MIN_ATTN = 0
+MAX_ATTN = 95
+ATTN_SLEEP = 12
+
+
+class WifiNetworkSelector6eTest(WifiNetworkSelectorTest):
+  """Tests for network selector 6e tests.
+
+  Test Bed Requirement:
+    1 Android devices, 2 Asus AXE11000 Access Point.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_network_selector_automatic_connection",
+        "test_network_selector_basic_connection_prefer_6g_over_2g",
+        "test_network_selector_basic_connection_prefer_6g_over_5g",
+        "test_network_selector_prefer_stronger_rssi",
+        "test_network_selector_excludelist_by_connection_failure",
+        "test_network_selector_stay_on_user_selected_network",
+        "test_network_selector_reselect_after_forget_network"
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    req_params = [
+        "reference_networks", "bssid_map", "wifi6_models", "attn_vals_6g"
+    ]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap1 = self.reference_networks[0]["6g"]
+    self.ap2 = self.reference_networks[1]["6g"]
+
+  ### Tests ###
+
+  @test_tracker_info(uuid="a5577d5c-3d4b-4c97-9dc2-25c0f6b54398")
+  def test_network_selector_automatic_connection(self):
+    """Network selection for automatic connection.
+
+    Steps:
+      1. Add a 6g network.
+      2. Move DUT in range.
+      3. Verify DUT is connected to the network.
+    """
+
+    # add a saved network to DUT
+    self.add_networks(self.dut, [self.ap1])
+
+    # move DUT in range
+    self.attenuators[1].set_atten(MIN_ATTN)
+    time.sleep(ATTN_SLEEP)
+
+    # verify DUT is connect to 6g network
+    network = self.ap1.copy()
+    network["bssid"] = self.bssid_map[0]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+  @test_tracker_info(uuid="29fc79fc-3b19-4e30-b325-4bc750be7eda")
+  def test_network_selector_basic_connection_prefer_6g_over_2g(self):
+    """Network selector prefers 6g network over 2g.
+
+    Steps:
+      1. Add two saved networks - 2g and 6g of similar RSSI.
+      2. Move DUT in range.
+      3. Verify DUT is connected to 6g network.
+    """
+    ap1 = self.reference_networks[1]["2g"]
+    ap2 = self.reference_networks[1]["6g"]
+
+    # add two saved networks
+    self.add_networks(self.dut, [ap1, ap2])
+
+    # move DUT in range
+    self.attenuators[2].set_atten(self.attn_vals_6g[2]["ATTN_DIFF"])
+    self.attenuators[3].set_atten(self.attn_vals_6g[3]["ATTN_DIFF"])
+    time.sleep(ATTN_SLEEP)
+
+    # verify DUT is connected to 6g network
+    network = ap2.copy()
+    network["bssid"] = self.bssid_map[1]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+  @test_tracker_info(uuid="a1c11fab-2878-45fc-8d1b-057fc6c72019")
+  def test_network_selector_basic_connection_prefer_6g_over_5g(self):
+    """Network selector prefers 6g network over 5g.
+
+    Steps:
+      1. Add two saved networks - 5g and 6g of similar RSSI.
+      2. Move DUT in range.
+      3. Verify DUT is connected to 6g network.
+    """
+    ap1 = self.reference_networks[1]["5g"]
+    ap2 = self.reference_networks[1]["6g"]
+
+    # add two saved networks
+    self.add_networks(self.dut, [ap1, ap2])
+
+    # move DUT in range
+    self.attenuators[2].set_atten(self.attn_vals_6g[2]["ATTN_DIFF"])
+    self.attenuators[3].set_atten(self.attn_vals_6g[3]["ATTN_DIFF"])
+    time.sleep(ATTN_SLEEP)
+
+    # verify DUT is connected to 6g network
+    network = ap2.copy()
+    network["bssid"] = self.bssid_map[1]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+  @test_tracker_info(uuid="7908a0a1-1786-4b8d-801c-a648bfaffaad")
+  def test_network_selector_prefer_stronger_rssi(self):
+    """Network selector test for stronger RSSI.
+
+    Steps:
+      1. Add two 6g networks, one with stronger RSSI.
+      2. Move DUT in range.
+      3. Verify DUT is connected to ap with stronger RSSI.
+    """
+
+    # add 2 6ghz networks.
+    self.add_networks(self.dut, [self.ap1, self.ap2])
+
+    # move DUT in range
+    self.attenuators[1].set_atten(MIN_ATTN)
+    self.attenuators[3].set_atten(LVL_ATTN)
+    time.sleep(ATTN_SLEEP)
+
+    # verify DUT is connected to ap1
+    network = self.ap1.copy()
+    network["bssid"] = self.bssid_map[0]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+  @test_tracker_info(uuid="b7adde45-224c-46d5-9948-f8739a586912")
+  def test_network_selector_excludelist_by_connection_failure(self):
+    """Network selector for excludelisted network.
+
+    Steps:
+      1. Add two saved networks one with stronger RSSI and wrong password.
+      2. Move DUT in range.
+      3. Verify DUT is connected to weaker and correct ap.
+    """
+    ap1 = self.ap1.copy()
+    ap1["password"] += "_haha"
+
+    # add the wifi networks.
+    self.add_networks(self.dut, [ap1, self.ap2])
+
+    # move the DUT in range.
+    self.attenuators[1].set_atten(MIN_ATTN)
+    self.attenuators[3].set_atten(LVL_ATTN)
+    time.sleep(ATTN_SLEEP)
+
+    # start scans to make ap1 excludelisted
+    for _ in range(3):
+      wutils.start_wifi_connection_scan_and_return_status(self.dut)
+      time.sleep(NETWORK_SELECTION_TIME_GAP)
+
+    # verify DUT is connected to ap2.
+    network = self.ap2.copy()
+    network["bssid"] = self.bssid_map[1]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+  @test_tracker_info(uuid="fae1a621-1302-4153-b51e-5d1a22018394")
+  def test_network_selector_stay_on_user_selected_network(self):
+    """Network selector with two saved 6GHz networks.
+
+    Steps:
+      1. Connect DUT to ap1 with low RSSI.
+      2. Add ap2 as saved network.
+      3. Start scan and verify DUT stays on ap1.
+    """
+
+    # set max attenuation on ap2 and make ap1 6G in range with low RSSI
+    self.attenuators[1].set_atten(LVL_ATTN)
+    self.attenuators[3].set_atten(MIN_ATTN)
+    time.sleep(ATTN_SLEEP)
+
+    # connect to ap1 via user selection and add, save ap2
+    wutils.connect_to_wifi_network(self.dut, self.ap1)
+    self.add_networks(self.dut, [self.ap2])
+
+    # ensure the time gap between two network selections
+    time.sleep(NETWORK_SELECTION_TIME_GAP)
+
+    # verify we are still connected to ap1
+    network = self.ap1.copy()
+    network["bssid"] = self.bssid_map[0]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+  @test_tracker_info(uuid="40d8ef8f-728b-43ac-8004-8847031a8198")
+  def test_network_selector_reselect_after_forget_network(self):
+    """Network selector with two 6GHz networks.
+
+    Steps:
+      1. Add two 6G networks X & Y to DUT with one having higher RSSI.
+      2. Connect to X and forget it.
+      3. Verify the DUT reselect and connect to Y.
+    """
+
+    # add two networks to DUT
+    self.add_networks(self.dut, [self.ap1, self.ap2])
+
+    # make both networks in range. AP1 has higher RSSI
+    self.attenuators[1].set_atten(MIN_ATTN)
+    self.attenuators[3].set_atten(LVL_ATTN)
+    time.sleep(ATTN_SLEEP)
+
+    # verify DUT connected to AP1
+    network = self.ap1.copy()
+    network["bssid"] = self.bssid_map[0]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
+    # forget AP_1
+    wutils.wifi_forget_network(self.dut, self.ap1["SSID"])
+
+    # verify DUT connected to AP2
+    network = self.ap2.copy()
+    network["bssid"] = self.bssid_map[1]["6g"]
+    self.connect_and_verify_connected_bssid(network)
+
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py
new file mode 100644
index 0000000..0bfc3da
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py
@@ -0,0 +1,80 @@
+#
+#   Copyright 2021 - 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_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiPnoTest import WifiPnoTest
+
+MAX_ATTN = 95
+
+
+class WifiPno6eTest(WifiPnoTest):
+  """Tests for pno for 6ghz.
+
+  Test Bed Requirement:
+    1 Android devices, 2 Asus AXE11000 Access Point.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_simple_pno_connection_to_6G",
+        "test_pno_connection_with_multiple_saved_networks",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    req_params = [
+        "attn_vals", "pno_interval", "reference_networks", "wifi6_models"
+    ]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.attn_a = self.attenuators[0]
+    self.attn_b = self.attenuators[1]
+    self.attenuators[2].set_atten(MAX_ATTN)
+    self.attenuators[3].set_atten(MAX_ATTN)
+    self.set_attns("default")
+    self.pno_network_a = self.reference_networks[0]["2g"]
+    self.pno_network_b = self.reference_networks[0]["6g"]
+
+  def teardown_class(self):
+    WifiBaseTest.teardown_class(self)
+    self.attenuators[2].set_atten(0)
+    self.attenuators[3].set_atten(0)
+
+  ### Tests ###
+
+  @test_tracker_info(uuid="4367138e-8c29-40f2-a963-44094223e525")
+  def test_simple_pno_connection_to_6G(self):
+    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="fb147268-87bb-4403-a971-ce3e0bd36d2c")
+  def test_pno_connection_with_multiple_saved_networks(self):
+    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)
+
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiRoaming6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiRoaming6eTest.py
new file mode 100644
index 0000000..753d864
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiRoaming6eTest.py
@@ -0,0 +1,142 @@
+#
+#   Copyright 2021 - 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.test_decorators import test_tracker_info
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class WifiRoaming6eTest(WifiBaseTest):
+  """Tests for 6e roaming tests.
+
+  Test Bed Requirement:
+    1 Android devices, 2 Asus AXE11000 Access Point.
+  """
+
+  def setup_class(self):
+    super().setup_class()
+    self.dut = self.android_devices[0]
+    self.ap1 = self.access_points[0]
+    self.ap2 = self.access_points[1]
+    req_params = ["roaming_attn_6g", "bssid_map"]
+    self.unpack_userparams(req_param_names=req_params,)
+
+  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()
+    wutils.set_attns(self.attenuators, "default")
+    self.dut.droid.wakeLockRelease()
+    self.dut.droid.goToSleepNow()
+    self.dut.ed.clear_all_events()
+    wutils.reset_wifi(self.dut)
+
+  ### Helper methods ###
+
+  def trigger_roaming_and_validate(self, attn_val, expected_conf):
+    """Trigger roaming and validate the DUT connected to expected BSSID.
+
+    Args:
+      attn_val: Attenuator value.
+      expected_conf: expected wifi network.
+    """
+    expected_con = {
+        "SSID": expected_conf["SSID"],
+        "BSSID": expected_conf["bssid"]
+    }
+    wutils.set_attns_steps(self.attenuators, attn_val, self.roaming_attn_6g)
+    self.log.info("Wait %ss for roaming to finish.")
+    self.dut.droid.wakeLockAcquireBright()
+    self.dut.droid.wakeUpNow()
+    wutils.verify_wifi_connection_info(self.dut, expected_con)
+    expected_bssid = expected_con["BSSID"]
+    self.log.info("Roamed to %s successfully", expected_bssid)
+
+  def roaming_between_ap1_and_ap2(self, ap1_network, ap2_network, a_on_b_off,
+                                  a_off_b_on):
+    """Roaming logic between ap1 and ap2.
+
+    Args:
+      ap1_network: Wifi config for AP1.
+      ap2_network: Wifi config for AP2.
+      a_on_b_off: Attenuator value to roam from b to a.
+      a_off_b_on: Attenautor value to roam from a to b.
+    """
+    ap1 = ap1_network.copy()
+    ap1.pop("bssid")
+    wutils.set_attns_steps(self.attenuators, a_on_b_off, self.roaming_attn_6g)
+    wutils.connect_to_wifi_network(self.dut, ap1)
+    self.log.info("Roaming from %s to %s", ap1_network, ap2_network)
+    self.trigger_roaming_and_validate(a_off_b_on, ap2_network)
+    self.log.info("Roaming from %s to %s", ap2_network, ap1_network)
+    self.trigger_roaming_and_validate(a_on_b_off, ap1_network)
+
+  ### Test cases ###
+
+  @test_tracker_info(uuid="805eed41-b596-4d51-9ae5-3cf784b2969a")
+  def test_roaming_between_6g_to_6g_sae(self):
+    roam_ssid = "test_%s" % "_".join(self.test_name.split("_")[-4:-1])
+    self.ap1.configure_ap({"6g": {"security": "sae", "SSID": roam_ssid}})
+    ap1_network = self.ap1.get_wifi_network("6g")
+    ap1_network["bssid"] = self.bssid_map[0]["6g"]
+    self.ap2.configure_ap({"6g": {"security": "sae", "SSID": roam_ssid}})
+    ap2_network = self.ap2.get_wifi_network("6g")
+    ap2_network["bssid"] = self.bssid_map[1]["6g"]
+    self.roaming_between_ap1_and_ap2(ap2_network, ap1_network,
+                                     "AP1_6G_OFF_AP2_6G_ON",
+                                     "AP1_6G_ON_AP2_6G_OFF")
+
+  @test_tracker_info(uuid="25e8a30e-4bfe-4572-b877-fde48f433668")
+  def test_roaming_between_6g_to_6g_owe(self):
+    roam_ssid = "test_%s" % "_".join(self.test_name.split("_")[-4:-1])
+    self.ap1.configure_ap({"6g": {"security": "owe", "SSID": roam_ssid}})
+    ap1_network = self.ap1.get_wifi_network("6g")
+    ap1_network["bssid"] = self.bssid_map[0]["6g"]
+    self.ap2.configure_ap({"6g": {"security": "owe", "SSID": roam_ssid}})
+    ap2_network = self.ap2.get_wifi_network("6g")
+    ap2_network["bssid"] = self.bssid_map[1]["6g"]
+    self.roaming_between_ap1_and_ap2(ap2_network, ap1_network,
+                                     "AP1_6G_OFF_AP2_6G_ON",
+                                     "AP1_6G_ON_AP2_6G_OFF")
+
+  @test_tracker_info(uuid="b1aa0057-4887-47fd-b29d-af7aadcdc3ab")
+  def test_roaming_between_6g_to_2g_sae(self):
+    roam_ssid = "test_%s" % "_".join(self.test_name.split("_")[-4:-1])
+    self.ap1.configure_ap({"6g": {"security": "sae", "SSID": roam_ssid}})
+    ap1_network = self.ap1.get_wifi_network("6g")
+    ap1_network["bssid"] = self.bssid_map[0]["6g"]
+    self.ap2.configure_ap({"2g": {"security": "sae", "SSID": roam_ssid}})
+    ap2_network = self.ap2.get_wifi_network("2g")
+    ap2_network["bssid"] = self.bssid_map[1]["2g"]
+    self.roaming_between_ap1_and_ap2(ap1_network, ap2_network,
+                                     "AP1_6G_OFF_AP2_2G_ON",
+                                     "AP1_6G_ON_AP2_2G_OFF")
+
+  @test_tracker_info(uuid="78318f92-3cf5-49b8-89b0-7969cf631ad3")
+  def test_roaming_between_6g_to_5g_sae(self):
+    roam_ssid = "test_%s" % "_".join(self.test_name.split("_")[-4:-1])
+    self.ap1.configure_ap({"6g": {"security": "sae", "SSID": roam_ssid}})
+    ap1_network = self.ap1.get_wifi_network("6g")
+    ap1_network["bssid"] = self.bssid_map[0]["6g"]
+    self.ap2.configure_ap({"5g": {"security": "sae", "SSID": roam_ssid}})
+    ap2_network = self.ap2.get_wifi_network("5g")
+    ap2_network["bssid"] = self.bssid_map[1]["5g"]
+    self.roaming_between_ap1_and_ap2(ap1_network, ap2_network,
+                                     "AP1_6G_OFF_AP2_5G_ON",
+                                     "AP1_6G_ON_AP2_5G_OFF")
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py
new file mode 100644
index 0000000..15a3264
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py
@@ -0,0 +1,147 @@
+#
+#   Copyright 2021 - 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
+import acts.signals as signals
+from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
+
+WifiEnums = wutils.WifiEnums
+WIFI_CONFIG_SOFTAP_BAND_2G = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G
+WIFI_CONFIG_SOFTAP_BAND_5G = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G
+WIFI_CONFIG_SOFTAP_BAND_2G_5G = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G
+
+
+class WifiStaApConcurrency6eTest(WifiStaApConcurrencyTest):
+  """Tests for sta+ap concurrency for 6ghz.
+
+  Test Bed Requirement:
+    2 Android devices, 1 Asus AXE11000 Access Point.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self.tests = (
+        "test_wifi_connection_6G_softap_2G",
+        "test_wifi_connection_6G_softap_5G",
+        "test_wifi_connection_6G_softap_2G_with_location_scan_on",
+        "test_softap_2G_wifi_connection_6G",
+        "test_softap_5G_wifi_connection_6G",
+        "test_softap_5G_wifi_connection_6G_with_location_scan_on",
+        "test_wifi_connection_6G_softap_2G_to_softap_5G",
+    )
+
+  def setup_class(self):
+    WifiBaseTest.setup_class(self)
+
+    self.dut = self.android_devices[0]
+    self.dut_client = self.android_devices[1]
+    req_params = [
+        "iperf_server_address", "iperf_server_port", "wifi6_models",
+        "dbs_supported_models"
+    ]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.ap1 = self.access_points[0]
+    self.wifi_network_6g = self.ap1.get_wifi_network("6g")
+
+  def teardown_test(self):
+    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)
+      wutils.wifi_toggle_state(ad, True)
+    self.turn_location_on_and_scan_toggle_on()
+
+  ### Helper Methods ###
+
+  def _verify_softap_band(self, expected_band):
+    """Verify softap band is correctly set.
+
+    Args:
+      expected_band: Expected softap band.
+    """
+    configured_band = self.dut.droid.wifiGetApConfiguration()["apBand"]
+    asserts.assert_true(configured_band == expected_band,
+                        "Expected softap band: %s, Configured softap band: %s",
+                        (expected_band, configured_band))
+
+  ### Tests ###
+
+  @test_tracker_info(uuid="bdd8cbf0-526d-4a20-b10b-3796f648f8d0")
+  def test_wifi_connection_6G_softap_2G(self):
+    """Test connection to 6G network followed by SoftAp on 2G."""
+    self.connect_to_wifi_network_and_start_softap(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_2G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G)
+
+  @test_tracker_info(uuid="94946b34-7cff-4582-a311-3e4e3690324b")
+  def test_wifi_connection_6G_softap_5G(self):
+    """Test connection to 6G network followed by SoftAp on 5G."""
+    self.connect_to_wifi_network_and_start_softap(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_5G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G_5G)
+
+  @test_tracker_info(uuid="34e85416-42b5-44b0-b97a-a4b0818e6cae")
+  def test_wifi_connection_6G_softap_2G_with_location_scan_on(self):
+    """Test connection to 6G network, SoftAp on 2G with location scan on."""
+    self.turn_location_on_and_scan_toggle_on()
+    self.connect_to_wifi_network_and_start_softap(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_2G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_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.wifi_network_6g[WifiEnums.SSID_KEY])
+
+  @test_tracker_info(uuid="9de42708-fa93-4901-8704-1411f435b256")
+  def test_softap_2G_wifi_connection_6G(self):
+    """Test SoftAp on 2G followed by connection to 6G network."""
+    self.start_softap_and_connect_to_wifi_network(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_2G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G)
+
+  @test_tracker_info(uuid="9cdafc82-e971-4610-9989-e1c1d39ad18f")
+  def test_softap_5G_wifi_connection_6G(self):
+    """Test SoftAp on 5G followed by connection to 6G network."""
+    self.start_softap_and_connect_to_wifi_network(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_5G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G_5G)
+
+  @test_tracker_info(uuid="bfad2e86-d1d7-44b3-b8c8-eb49f3f97742")
+  def test_softap_5G_wifi_connection_6G_with_location_scan_on(self):
+    """Test SoftAp on 5G, connection to 6G network with location scan on."""
+    self.turn_location_on_and_scan_toggle_on()
+    self.start_softap_and_connect_to_wifi_network(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_5G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G_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.wifi_network_6g[WifiEnums.SSID_KEY])
+
+  @test_tracker_info(uuid="e50a0901-e07c-4b6f-9343-35e0dec14f2b")
+  def test_wifi_connection_6G_softap_2G_to_softap_5G(self):
+    """Test connection to 6G network, softap on 2G and switch to 5G."""
+    self.connect_to_wifi_network_and_start_softap(self.wifi_network_6g,
+                                                  WIFI_CONFIG_SOFTAP_BAND_2G)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G)
+    self.softap_change_band(self.dut)
+    self._verify_softap_band(WIFI_CONFIG_SOFTAP_BAND_2G_5G)
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py
new file mode 100644
index 0000000..066abdd
--- /dev/null
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py
@@ -0,0 +1,60 @@
+#
+#   Copyright 2021 - 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.test_decorators import test_tracker_info
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from WifiTeleCoexTest import WifiTeleCoexTest
+
+
+class WifiTeleCoex6eTest(WifiTeleCoexTest):
+  """Tests for wifi tel coex 6e tests.
+
+  Test Bed Requirement:
+    1 Android devices, 1 Asus AXE11000 Access Point, 1 Carrier SIM.
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+
+    self.tests = (
+        "test_toggle_wifi_call_6e",
+        "test_toggle_airplane_call_6e",
+        "test_toggle_airplane_and_wifi_call_6e",
+    )
+
+  def setup_class(self):
+    TelephonyBaseTest.setup_class(self)
+
+    self.ads = self.android_devices
+    self.dut = self.android_devices[0]
+    req_params = ["reference_networks",]
+    self.unpack_userparams(req_param_names=req_params,)
+    self.network = self.reference_networks[0]["6g"]
+    self.wifi_network_ssid = self.network["SSID"]
+
+  ### Tests ###
+
+  @test_tracker_info(uuid="2ca35dcd-f449-4e74-9d5a-9238168f25a5")
+  def test_toggle_wifi_call_6e(self):
+    super().test_toggle_wifi_call()
+
+  @test_tracker_info(uuid="d819a404-609b-477a-95f6-9fc68ce04947")
+  def test_toggle_airplane_call_6e(self):
+    super().test_toggle_airplane_call()
+
+  @test_tracker_info(uuid="bc802fc9-2763-4789-94b0-15655333af81")
+  def test_toggle_airplane_and_wifi_call_6e(self):
+    super().test_toggle_airplane_and_wifi_call()